429 lines
13 KiB
JavaScript
429 lines
13 KiB
JavaScript
|
|
/**
|
|||
|
|
* ═══════════════════════════════════════════════════════════
|
|||
|
|
* 模块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
|
|||
|
|
};
|