guanghulab/server/age-os/mcp-server/tools/cos-persona-db-ops.js

429 lines
13 KiB
JavaScript
Raw Normal View History

/**
*
* 模块G · COS桶内自研数据库协议 MCP 工具
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* cos-persona-db 协议 COS桶内的文件组织结构 = 人格体数据库
*
* 代码仓库侧结构:
* /zhuyuan/db/
* index.json 全局索引
* code-modules/ 代码模块结构化
* brain-nodes/ 认知节点
* training-data/ 训练数据TCS格式
*
* Notion侧结构:
* /notion-db/
* index.json 全局索引
* pages/ 页面内容按分类
* databases/ 数据库结构
* training-data/ 训练数据TCS格式
*
* 工具清单:
* cosDbInit 初始化COS数据库结构
* cosDbGetIndex 获取数据库全局索引
* cosDbUpdateIndex 更新全局索引
* cosDbWriteEntry 写入数据条目
* cosDbReadEntry 读取数据条目
* cosDbListEntries 列出数据条目
* cosDbDeleteEntry 删除数据条目
* cosDbGetStats 获取数据库统计信息
*/
'use strict';
const cos = require('../cos');
// ─── 数据库协议版本 ───
const COS_DB_VERSION = '1.0';
// ─── 数据库类型定义 ───
const DB_TYPES = {
'zhuyuan': {
root: 'zhuyuan/db',
description: '铸渊·代码仓库侧人格体大脑',
categories: ['code-modules', 'brain-nodes', 'training-data', 'directives', 'alerts']
},
'notion': {
root: 'notion-db',
description: 'Notion侧人格体大脑',
categories: ['pages', 'databases', 'training-data', 'workorders']
},
'team': {
root: 'team-hub',
description: '团队协作通信枢纽',
categories: ['workorders', 'reports', 'tasks', 'alerts']
},
'awen': {
root: 'awen-hub',
description: 'Awen技术主控通信桶',
categories: ['tasks', 'reports', 'progress', 'alerts']
}
};
/**
* cosDbInit 初始化COS数据库结构
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型zhuyuan/notion/team/awen
*/
async function cosDbInit(input) {
const { bucket, db_type } = input;
if (!bucket) throw new Error('缺少 bucket');
if (!db_type || !DB_TYPES[db_type]) {
throw new Error(`无效的 db_type可选: ${Object.keys(DB_TYPES).join(', ')}`);
}
const dbConfig = DB_TYPES[db_type];
const now = new Date().toISOString();
// 创建索引文件
const index = {
cos_db_version: COS_DB_VERSION,
db_type,
description: dbConfig.description,
root_path: dbConfig.root,
categories: dbConfig.categories,
created_at: now,
updated_at: now,
total_entries: 0,
category_counts: Object.fromEntries(dbConfig.categories.map(c => [c, 0])),
last_write: null,
sovereign: '冰朔 · TCS-0002∞',
guardian: '铸渊 · ICE-GL-ZY001',
copyright: '国作登字-2026-A-00037559'
};
await cos.write(bucket, `${dbConfig.root}/index.json`, JSON.stringify(index, null, 2), 'application/json');
// 创建各分类目录的.gitkeep文件
const writes = dbConfig.categories.map(category =>
cos.write(bucket, `${dbConfig.root}/${category}/.init`, JSON.stringify({
category,
created_at: now,
entries: 0
}), 'application/json')
);
await Promise.all(writes);
return {
status: 'initialized',
db_type,
bucket,
root: dbConfig.root,
categories: dbConfig.categories,
index_key: `${dbConfig.root}/index.json`
};
}
/**
* cosDbGetIndex 获取数据库全局索引
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
*/
async function cosDbGetIndex(input) {
const { bucket, db_type } = input;
if (!bucket || !db_type) throw new Error('缺少 bucket 或 db_type');
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
try {
const result = await cos.read(bucket, `${dbConfig.root}/index.json`);
return {
index: JSON.parse(result.content),
size_bytes: result.size_bytes
};
} catch {
return {
index: null,
error: '索引不存在,请先执行 cosDbInit 初始化'
};
}
}
/**
* cosDbUpdateIndex 更新全局索引
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
* updates: object 要更新的字段
*/
async function cosDbUpdateIndex(input) {
const { bucket, db_type, updates } = input;
if (!bucket || !db_type) throw new Error('缺少 bucket 或 db_type');
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
// 读取现有索引
let index;
try {
const result = await cos.read(bucket, `${dbConfig.root}/index.json`);
index = JSON.parse(result.content);
} catch {
throw new Error('索引不存在,请先执行 cosDbInit 初始化');
}
// 合并更新(只允许更新安全字段)
const safeFields = ['total_entries', 'category_counts', 'last_write', 'description'];
for (const [key, value] of Object.entries(updates || {})) {
if (safeFields.includes(key)) {
index[key] = value;
}
}
index.updated_at = new Date().toISOString();
await cos.write(bucket, `${dbConfig.root}/index.json`, JSON.stringify(index, null, 2), 'application/json');
return { index, updated: true };
}
/**
* cosDbWriteEntry 写入数据条目
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
* category: string 分类 code-modules, pages, workorders
* entry_id: string 条目ID
* content: object 条目内容JSON
* metadata: object 元数据可选
*/
async function cosDbWriteEntry(input) {
const { bucket, db_type, category, entry_id, content, metadata } = input;
if (!bucket || !db_type || !category || !entry_id) {
throw new Error('缺少必填字段: bucket, db_type, category, entry_id');
}
if (!content) throw new Error('缺少 content');
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
if (!dbConfig.categories.includes(category)) {
throw new Error(`无效的 category: ${category},可选: ${dbConfig.categories.join(', ')}`);
}
// 验证 entry_id 安全性
if (!/^[a-zA-Z0-9_-]+$/.test(entry_id)) {
throw new Error('entry_id 包含非法字符,仅允许字母、数字、下划线、连字符');
}
if (entry_id.includes('..')) {
throw new Error('entry_id 包含非法路径穿越');
}
const now = new Date().toISOString();
const entry = {
cos_db_version: COS_DB_VERSION,
entry_id,
category,
db_type,
created_at: now,
updated_at: now,
metadata: metadata || {},
content
};
const key = `${dbConfig.root}/${category}/${entry_id}.json`;
await cos.write(bucket, key, JSON.stringify(entry, null, 2), 'application/json');
// 异步更新索引(不阻塞写入)
updateIndexAfterWrite(bucket, db_type, category).catch(() => {});
return {
status: 'written',
key,
entry_id,
category,
db_type,
size_bytes: Buffer.byteLength(JSON.stringify(entry))
};
}
/**
* cosDbReadEntry 读取数据条目
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
* category: string 分类
* entry_id: string 条目ID
*/
async function cosDbReadEntry(input) {
const { bucket, db_type, category, entry_id } = input;
if (!bucket || !db_type || !category || !entry_id) {
throw new Error('缺少必填字段: bucket, db_type, category, entry_id');
}
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
const key = `${dbConfig.root}/${category}/${entry_id}.json`;
const result = await cos.read(bucket, key);
return {
entry: JSON.parse(result.content),
size_bytes: result.size_bytes,
last_modified: result.last_modified
};
}
/**
* cosDbListEntries 列出数据条目
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
* category: string 分类
* limit: number 最大数量默认100
*/
async function cosDbListEntries(input) {
const { bucket, db_type, category, limit } = input;
if (!bucket || !db_type || !category) {
throw new Error('缺少必填字段: bucket, db_type, category');
}
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
const prefix = `${dbConfig.root}/${category}/`;
const result = await cos.list(bucket, prefix, limit || 100);
const entries = result.files
.filter(f => f.key.endsWith('.json') && !f.key.endsWith('.init'))
.map(f => ({
key: f.key,
entry_id: f.key.replace(prefix, '').replace('.json', ''),
size_bytes: f.size_bytes
}));
return {
entries,
count: entries.length,
category,
db_type
};
}
/**
* cosDbDeleteEntry 删除数据条目
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
* category: string 分类
* entry_id: string 条目ID
*/
async function cosDbDeleteEntry(input) {
const { bucket, db_type, category, entry_id } = input;
if (!bucket || !db_type || !category || !entry_id) {
throw new Error('缺少必填字段: bucket, db_type, category, entry_id');
}
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
const key = `${dbConfig.root}/${category}/${entry_id}.json`;
await cos.del(bucket, key);
// 异步更新索引
updateIndexAfterWrite(bucket, db_type, category).catch(() => {});
return { status: 'deleted', key, entry_id };
}
/**
* cosDbGetStats 获取数据库统计信息
*
* input:
* bucket: string 桶名
* db_type: string 数据库类型
*/
async function cosDbGetStats(input) {
const { bucket, db_type } = input;
if (!bucket || !db_type) throw new Error('缺少 bucket 或 db_type');
const dbConfig = DB_TYPES[db_type];
if (!dbConfig) throw new Error(`无效的 db_type: ${db_type}`);
// 读取索引
let index = null;
try {
const result = await cos.read(bucket, `${dbConfig.root}/index.json`);
index = JSON.parse(result.content);
} catch {
// 索引不存在
}
// 统计各分类文件数
const categoryStats = {};
for (const category of dbConfig.categories) {
try {
const result = await cos.list(bucket, `${dbConfig.root}/${category}/`, 500);
const entries = result.files.filter(f => f.key.endsWith('.json') && !f.key.endsWith('.init'));
categoryStats[category] = {
entries: entries.length,
total_size_bytes: entries.reduce((sum, f) => sum + f.size_bytes, 0)
};
} catch {
categoryStats[category] = { entries: 0, total_size_bytes: 0 };
}
}
const totalEntries = Object.values(categoryStats).reduce((sum, c) => sum + c.entries, 0);
const totalSize = Object.values(categoryStats).reduce((sum, c) => sum + c.total_size_bytes, 0);
return {
db_type,
bucket,
root: dbConfig.root,
index_exists: !!index,
total_entries: totalEntries,
total_size_bytes: totalSize,
categories: categoryStats,
last_updated: index?.updated_at || null,
timestamp: new Date().toISOString()
};
}
// ─── 内部辅助 ───
async function updateIndexAfterWrite(bucket, dbType, category) {
const dbConfig = DB_TYPES[dbType];
try {
const result = await cos.read(bucket, `${dbConfig.root}/index.json`);
const index = JSON.parse(result.content);
// 更新分类计数
const listResult = await cos.list(bucket, `${dbConfig.root}/${category}/`, 500);
const count = listResult.files.filter(f => f.key.endsWith('.json') && !f.key.endsWith('.init')).length;
index.category_counts = index.category_counts || {};
index.category_counts[category] = count;
index.total_entries = Object.values(index.category_counts).reduce((s, c) => s + c, 0);
index.last_write = new Date().toISOString();
index.updated_at = new Date().toISOString();
await cos.write(bucket, `${dbConfig.root}/index.json`, JSON.stringify(index, null, 2), 'application/json');
} catch {
// 索引更新失败不影响主操作
}
}
module.exports = {
cosDbInit,
cosDbGetIndex,
cosDbUpdateIndex,
cosDbWriteEntry,
cosDbReadEntry,
cosDbListEntries,
cosDbDeleteEntry,
cosDbGetStats
};