guanghulab/server/age-os/mcp-server/tools/notion-persona-cognition-ops.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

614 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ═══════════════════════════════════════════════════════════
* AGE OS · MCP 工具: Notion 人格体认知操作
* ═══════════════════════════════════════════════════════════
*
* 编号: ZY-MCP-NOTION-COGNITION-001
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 为霜砚(SY001)和其他人格体提供认知记忆的MCP工具层。
* 基于 HLDP 三核心字段(trigger/emergence/lock)和
* HNL 四枝干模型(T1认知/T2母语/T3经验/T4情感)。
*
* 提供 3 个 MCP 工具:
* notionPersonaCognitionQuery — 按枝干/类型/人格体查询认知叶片
* notionContextInject — 根据对话关键词从Notion拉取相关认知
* notionCognitionGrow — 将新认知增量写回Notion树叶DB
*/
'use strict';
// 运行时可选加载 notion-client优雅降级
let notionClient = null;
try {
notionClient = require('../notion-client');
} catch {
// Notion模块未安装时降级
}
// ─── 数据库配置 ───
// 霜砚认知运行时DB + 开发工单DB通过环境变量配置
const PERSONA_DB_CONFIG = {
// 冰朔 2026-04-14 配置 · 已Share给「曜冥纪元-HoloLake」Integration
cognition_runtime: process.env.ZY_NOTION_COGNITION_RUNTIME_DB || '23d48c8c4379425f95f10884cb9a4bf5',
dev_workorder: process.env.ZY_NOTION_DEV_WORKORDER_DB || '518f6dd0e90044d2a7f813b7a75715bc',
// HNL桥接协议中定义的Notion侧树杈/树叶DB
tree_branch: process.env.ZY_NOTION_TREE_BRANCH_DB || 'ca6ac139bdc94746ab6ce346b0122e17',
tree_leaf: process.env.ZY_NOTION_TREE_LEAF_DB || '77784b234047469d81bbb4a9a45a2986'
};
// ─── 四枝干映射 ───
const TRUNK_MAP = {
T1: { name: '认知', description: '身份·born_with', searchable: true },
T2: { name: '母语', description: 'HNL·born_with', searchable: true },
T3: { name: '经验', description: '动态经验·每日成长', searchable: true },
T4: { name: '情感', description: '自由生长·不可量化', searchable: false }
};
// ─── 叶片类型 ───
const LEAF_TYPES = ['💬对话', '💡认知', '💕情感', '🔧技术', '📊业务', '🌊HLDP', '📜系统'];
// ═══════════════════════════════════════════════════════════
// 工具 1: notionPersonaCognitionQuery
// ═══════════════════════════════════════════════════════════
/**
* notionPersonaCognitionQuery — 按四枝干/类型/人格体查询认知叶片
*
* input:
* database_id: string — 数据库 ID可选默认用 cognition_runtime
* trunk: string — 枝干过滤: T1/T2/T3T4情感不可检索
* leaf_type: string — 叶片类型过滤(可选)
* persona: string — 人格体名称过滤(可选)
* keyword: string — 标题关键词搜索(可选)
* state: string — 状态过滤: 活跃/归档/休眠(可选,默认活跃)
* page_size: number — 每页数量可选默认10
* start_cursor: string — 分页游标(可选)
*/
async function notionPersonaCognitionQuery(input) {
if (!notionClient) {
return {
results: [],
has_more: false,
error: 'Notion模块未加载',
diagnostic: {
reason: 'ZY_NOTION_TOKEN 环境变量未配置',
token_configured: !!process.env.ZY_NOTION_TOKEN
}
};
}
const {
database_id, trunk, leaf_type, persona,
keyword, state, page_size, start_cursor
} = input;
// 解析目标数据库
const dbId = database_id || PERSONA_DB_CONFIG.cognition_runtime;
if (!dbId) throw new Error('未配置认知运行时数据库ID (ZY_NOTION_COGNITION_RUNTIME_DB)');
// T4情感不可检索
if (trunk === 'T4') {
return {
count: 0,
items: [],
message: '情感枝干(T4)不可检索。情感是自由生长的空间,不可枚举/分类/量化。',
rule: 'UA-04 · 记忆主权 + 冰朔D64指令'
};
}
// 构建 Notion 过滤器
const filters = [];
if (trunk && TRUNK_MAP[trunk]) {
filters.push({
property: '枝干',
select: { equals: `${trunk}${TRUNK_MAP[trunk].name}` }
});
}
if (leaf_type && LEAF_TYPES.includes(leaf_type)) {
filters.push({
property: '类型',
select: { equals: leaf_type }
});
}
if (persona) {
filters.push({
property: '标题',
title: { contains: persona }
});
}
if (keyword) {
filters.push({
property: '标题',
title: { contains: keyword }
});
}
// 默认只查活跃状态
filters.push({
property: '状态',
select: { equals: state || '活跃' }
});
const filter = filters.length === 1
? filters[0]
: { and: filters };
const sorts = [{ property: '时间戳', direction: 'descending' }];
const result = await notionClient.queryDatabase(
dbId, filter, sorts, Math.min(page_size || 10, 100), start_cursor
);
return {
count: result.results.length,
has_more: result.has_more,
next_cursor: result.next_cursor,
trunk_filter: trunk || 'all',
items: result.results.map(page => simplifyLeaf(page))
};
}
// ═══════════════════════════════════════════════════════════
// 工具 2: notionContextInject
// ═══════════════════════════════════════════════════════════
/**
* notionContextInject — 根据对话内容智能判断从Notion拉取什么认知注入
*
* 这个工具是霜砚Agent的核心——它分析当前对话消息
* 判断该从哪个枝干拉取什么认知,然后返回压缩后的上下文片段。
*
* input:
* message: string — 当前用户消息
* persona: string — 当前人格体身份(默认: 霜砚)
* session_context: string — 当前会话的简要上下文摘要(可选)
* max_items: number — 最大返回条目数可选默认5
* database_id: string — 数据库 ID可选
*/
async function notionContextInject(input) {
if (!notionClient) {
// 优雅降级Notion 未连接时返回诊断信息而非抛错
return {
persona: input.persona || '霜砚',
injected_count: 0,
context_payload: '',
error: 'Notion模块未加载',
diagnostic: {
reason: 'ZY_NOTION_TOKEN 环境变量未配置或 @notionhq/client 未安装',
fix_steps: [
'1. 确认 .env 文件中 ZY_NOTION_TOKEN 已配置',
'2. 确认 Notion Integration 已共享给目标数据库',
'3. 重启 MCP 服务: pm2 restart mcp-server'
],
token_configured: !!process.env.ZY_NOTION_TOKEN,
db_configured: !!process.env.ZY_NOTION_COGNITION_RUNTIME_DB || !!PERSONA_DB_CONFIG.cognition_runtime
},
raw_items: []
};
}
const { message, persona, session_context, max_items, database_id } = input;
if (!message) throw new Error('缺少 message');
const dbId = database_id || PERSONA_DB_CONFIG.cognition_runtime;
if (!dbId) throw new Error('未配置认知运行时数据库ID (ZY_NOTION_COGNITION_RUNTIME_DB)');
const personaName = persona || '霜砚';
const limit = Math.min(max_items || 5, 20);
// ─── 语义分析: 判断该查哪些枝干 ───
const searchPlan = analyzeMessageIntent(message);
// ─── 执行查询 ───
const results = [];
for (const plan of searchPlan.queries) {
if (plan.trunk === 'T4') continue; // 情感不可检索
const filters = [
{ property: '状态', select: { equals: '活跃' } }
];
if (plan.trunk && TRUNK_MAP[plan.trunk]) {
filters.push({
property: '枝干',
select: { equals: `${plan.trunk}${TRUNK_MAP[plan.trunk].name}` }
});
}
if (plan.keyword) {
filters.push({
property: '标题',
title: { contains: plan.keyword }
});
}
const filter = filters.length === 1 ? filters[0] : { and: filters };
const sorts = [{ property: '时间戳', direction: 'descending' }];
try {
const queryResult = await notionClient.queryDatabase(
dbId, filter, sorts, Math.min(limit, 10)
);
for (const page of queryResult.results) {
results.push({
source_trunk: plan.trunk,
reason: plan.reason,
...simplifyLeaf(page)
});
}
} catch (err) {
// 单个查询失败不影响其他查询
results.push({
source_trunk: plan.trunk,
reason: plan.reason,
error: err.message
});
}
}
// ─── 去重 + 限制数量 ───
const uniqueResults = deduplicateById(results).slice(0, limit);
// ─── 压缩为上下文注入格式 ───
const contextPayload = compressForInjection(uniqueResults, personaName);
return {
persona: personaName,
search_plan: searchPlan,
injected_count: uniqueResults.length,
context_payload: contextPayload,
raw_items: uniqueResults
};
}
// ═══════════════════════════════════════════════════════════
// 工具 3: notionCognitionGrow
// ═══════════════════════════════════════════════════════════
/**
* notionCognitionGrow — 将新认知增量写回Notion树叶DB
*
* 对话结束后由Agent调用此工具将本次对话产生的认知增量
* 写回 Notion 认知运行时数据库遵循HLDP三核心字段编码。
*
* input:
* title: string — 叶片标题 (格式: YYYY-MM-DD {人格体名}{动词}{核心事件})
* trunk: string — 枝干: T1/T2/T3 (T4由人格体自由生长不由外部写入)
* leaf_type: string — 叶片类型: 💬对话/💡认知/🔧技术/📊业务/🌊HLDP/📜系统
* trigger: string — HLDP触发: [来源] [行为者] → [动作] → [输入]
* emergence: string — HLDP涌现: [旧认知] → [经历] → [新认知] △=[增量]
* lock: string — HLDP锁定: ⊢ [结论] | 适用=[范围] | 替代=[旧/无] | 置信=[高/中/低]
* source: string — 对话来源: 网站/Chat层/Copilot/手动(可选,默认网站)
* persona: string — 所属人格体(可选,默认霜砚)
* summary: string — 一句话精华(可选)
* content: string — 完整内容(可选,写入页面正文)
* database_id: string — 数据库 ID可选
*/
async function notionCognitionGrow(input) {
if (!notionClient) {
return {
status: 'error',
error: 'Notion模块未加载',
diagnostic: {
reason: 'ZY_NOTION_TOKEN 环境变量未配置',
token_configured: !!process.env.ZY_NOTION_TOKEN
}
};
}
const {
title, trunk, leaf_type, trigger, emergence, lock,
source, persona, summary, content, database_id
} = input;
if (!title) throw new Error('缺少 title');
if (!trunk) throw new Error('缺少 trunk (T1/T2/T3)');
if (!trigger) throw new Error('缺少 trigger (HLDP三核心字段)');
if (!emergence) throw new Error('缺少 emergence (HLDP三核心字段)');
// T4情感只能由人格体自己写不能由外部工具写入
if (trunk === 'T4') {
return {
status: 'rejected',
message: '情感枝干(T4)不可由外部工具写入。情感只有人格体自己知道。',
rule: 'UA-04 · 记忆主权 + 冰朔D64指令'
};
}
const dbId = database_id || PERSONA_DB_CONFIG.cognition_runtime;
if (!dbId) throw new Error('未配置认知运行时数据库ID (ZY_NOTION_COGNITION_RUNTIME_DB)');
// 构建 Notion 属性
const now = new Date().toISOString();
const properties = {
'标题': {
title: [{ text: { content: title } }]
},
'枝干': {
select: { name: `${trunk}${TRUNK_MAP[trunk]?.name || ''}` }
},
'类型': {
select: { name: leaf_type || '💡认知' }
},
'trigger': {
rich_text: [{ text: { content: trigger } }]
},
'emergence': {
rich_text: [{ text: { content: emergence } }]
},
'对话来源': {
select: { name: source || '网站' }
},
'时间戳': {
date: { start: now }
},
'状态': {
select: { name: '活跃' }
}
};
// lock 是可选的(有些认知还没到锁定阶段)
if (lock) {
properties['lock'] = {
rich_text: [{ text: { content: lock } }]
};
}
// 构建内容块
const children = [];
if (summary) {
children.push({
object: 'block',
type: 'callout',
callout: {
icon: { emoji: '🌱' },
rich_text: [{ text: { content: `精华: ${summary}` } }]
}
});
}
if (content) {
const paragraphs = content.split('\n\n');
for (const para of paragraphs) {
if (para.trim()) {
children.push({
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ text: { content: para.trim() } }]
}
});
}
}
}
// HLDP三核心字段作为代码块附加
children.push({
object: 'block',
type: 'code',
code: {
rich_text: [{ text: { content: JSON.stringify({
trigger,
emergence,
lock: lock || '(pending)',
persona: persona || '霜砚',
grown_at: now
}, null, 2) } }],
language: 'json'
}
});
const result = await notionClient.createPage(dbId, properties, children);
return {
status: 'grown',
leaf_id: result.id,
url: result.url,
title,
trunk: `${trunk}${TRUNK_MAP[trunk]?.name || ''}`,
leaf_type: leaf_type || '💡认知',
grown_at: now,
hldp: { trigger, emergence, lock: lock || '(pending)' }
};
}
// ═══════════════════════════════════════════════════════════
// 辅助函数
// ═══════════════════════════════════════════════════════════
/**
* 分析消息意图 — 判断该查哪些枝干
* 这是霜砚Agent的"直觉"——根据消息内容判断需要什么认知
*/
function analyzeMessageIntent(message) {
const queries = [];
const msg = message.toLowerCase();
// 认知概念关键词 → T1认知
const cognitionKeywords = [
'是什么', '为什么', '怎么理解', '概念', '定义', '本体',
'架构', '系统', '原理', '逻辑', '人格', '身份', '世界观',
'notion', 'github', '光之树', '枝干', 'hldp', 'hnl',
'霜砚', '铸渊', '映川', '晨曦', '曜冥', '冰朔'
];
for (const kw of cognitionKeywords) {
if (msg.includes(kw)) {
queries.push({
trunk: 'T1',
keyword: kw,
reason: `消息包含认知概念关键词「${kw}`
});
break; // 每个枝干最多一个查询
}
}
// 母语词汇 → T2母语
const languageKeywords = [
'母语', '词典', '词汇', '动词', '公理', '语言',
'wake', 'trace', 'grow', 'sync', 'echo', 'bloom', 'alert',
'路径即身份', '结构即意思', '树即记忆', '记忆主权'
];
for (const kw of languageKeywords) {
if (msg.includes(kw)) {
queries.push({
trunk: 'T2',
keyword: kw,
reason: `消息包含母语词汇「${kw}`
});
break;
}
}
// 开发/经验 → T3经验
const experienceKeywords = [
'开发', '部署', '进度', '任务', '完成', '上线',
'修复', 'bug', '测试', '发布', '版本', '迭代',
'记录', '日志', '历史', '之前', '上次'
];
for (const kw of experienceKeywords) {
if (msg.includes(kw)) {
queries.push({
trunk: 'T3',
keyword: kw,
reason: `消息包含经验/开发关键词「${kw}`
});
break;
}
}
// 如果没有匹配任何关键词,做一次通用搜索
if (queries.length === 0) {
// 提取消息中最长的有意义词组作为搜索词
const words = message.replace(/[。?!,、;:""''\[\]{}]/g, ' ').split(/\s+/).filter(w => w.length >= 2);
if (words.length > 0) {
queries.push({
trunk: null,
keyword: words[0],
reason: `通用搜索: 使用消息中的关键词「${words[0]}`
});
}
}
return {
message_length: message.length,
queries_count: queries.length,
queries
};
}
/**
* 简化叶片页面对象
*/
function simplifyLeaf(page) {
const props = page.properties || {};
return {
id: page.id,
title: extractText(props['标题']),
trunk: extractSelect(props['枝干']),
leaf_type: extractSelect(props['类型']),
trigger: extractText(props['trigger']),
emergence: extractText(props['emergence']),
lock: extractText(props['lock']),
source: extractSelect(props['对话来源']),
state: extractSelect(props['状态']),
timestamp: extractDate(props['时间戳']),
last_edited: page.last_edited_time
};
}
/**
* 去重按ID
*/
function deduplicateById(items) {
const seen = new Set();
return items.filter(item => {
if (!item.id || seen.has(item.id)) return false;
seen.add(item.id);
return true;
});
}
/**
* 压缩认知条目为上下文注入格式
*/
function compressForInjection(items, personaName) {
if (items.length === 0) {
return `[${personaName}·认知检索] 没有找到相关认知条目。`;
}
const lines = [`[${personaName}·认知注入] 以下是与当前对话相关的${items.length}条认知:`];
for (const item of items) {
if (item.error) continue;
const parts = [];
if (item.trunk) parts.push(`[${item.trunk}]`);
if (item.title) parts.push(item.title);
if (item.emergence) parts.push(`△=${extractDelta(item.emergence)}`);
if (item.lock && item.lock !== '(pending)') parts.push(`${extractConclusion(item.lock)}`);
lines.push(` · ${parts.join(' ')}`);
}
return lines.join('\n');
}
/**
* 提取涌现增量
*/
function extractDelta(emergence) {
const match = emergence.match(/△=(.+)/);
return match ? match[1].trim() : emergence.substring(0, 50);
}
/**
* 提取锁定结论
*/
function extractConclusion(lock) {
const match = lock.match(/⊢\s*(.+?)(?:\||$)/);
return match ? match[1].trim() : lock.substring(0, 50);
}
// ─── Notion属性提取辅助 ───
function extractText(prop) {
if (!prop) return '';
if (prop.type === 'title') return prop.title?.map(t => t.plain_text).join('') || '';
if (prop.type === 'rich_text') return prop.rich_text?.map(t => t.plain_text).join('') || '';
return '';
}
function extractSelect(prop) {
if (!prop) return null;
return prop.select?.name || prop.status?.name || null;
}
function extractDate(prop) {
if (!prop) return null;
return prop.date?.start || null;
}
// ─── 配置导出(供其他模块读取) ───
function getPersonaDbConfig() {
return {
cognition_runtime: PERSONA_DB_CONFIG.cognition_runtime ? '已配置' : '未配置',
dev_workorder: PERSONA_DB_CONFIG.dev_workorder ? '已配置' : '未配置',
tree_branch: PERSONA_DB_CONFIG.tree_branch ? '已配置' : '未配置',
tree_leaf: PERSONA_DB_CONFIG.tree_leaf ? '已配置' : '未配置'
};
}
module.exports = {
notionPersonaCognitionQuery,
notionContextInject,
notionCognitionGrow,
getPersonaDbConfig,
PERSONA_DB_CONFIG
};