feat: 执行层系统升级 v5.0 — 新增核心模块与连接器
新增模块: - core/broadcast-listener: 广播监听与任务解析 - core/task-queue: 任务队列与调度系统 - core/system-check: 仓库自检系统 - connectors/notion-sync: Notion 双向同步 - connectors/model-router: 模型调用路由 新增文档: - docs/repo-structure-map.md: 仓库结构地图 - docs/notion-bridge-map.md: Notion 桥接地图 更新: - brain/master-brain.md: v4.0 → v5.0 - brain/system-health.json: 新增模块状态 - package.json: 新增 core/connector npm scripts - .gitignore: 排除运行时产物 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
6def63a0e6
commit
e0d72b5844
|
|
@ -19,3 +19,8 @@ modules/palace-game/data/saves/PAL-*
|
|||
|
||||
# collaboration-logs exports (generated by save-collaboration-log.js)
|
||||
collaboration-logs/exports/
|
||||
|
||||
# core runtime artifacts
|
||||
core/task-queue/queue.json
|
||||
core/task-queue/execution-log.json
|
||||
core/system-check/last-report.json
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# 铸渊执行层 · 系统导航主文件
|
||||
# Master Brain · v4.0
|
||||
# 数字地球系统通信协议 v4.0
|
||||
# Master Brain · v5.0
|
||||
# 数字地球系统通信协议 v5.0 — AGE-5 升级版
|
||||
|
||||
---
|
||||
|
||||
## 系统版本
|
||||
|
||||
**v4.0** — 数字地球系统通信协议
|
||||
**v5.0** — 数字地球系统通信协议 · 仓库执行层系统升级
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -60,6 +60,20 @@
|
|||
|
||||
---
|
||||
|
||||
## v5.0 升级模块
|
||||
|
||||
| 入口 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| 广播监听 | `core/broadcast-listener/index.js` | 广播监听与任务解析 |
|
||||
| 任务队列 | `core/task-queue/index.js` | 任务调度与执行 |
|
||||
| 系统自检 | `core/system-check/index.js` | 仓库自检系统 |
|
||||
| Notion 同步 | `connectors/notion-sync/index.js` | 双向数据同步 |
|
||||
| 模型路由 | `connectors/model-router/index.js` | 模型调用路由 |
|
||||
| 结构地图 | `docs/repo-structure-map.md` | 仓库结构文档 |
|
||||
| 桥接地图 | `docs/notion-bridge-map.md` | Notion 桥接文档 |
|
||||
|
||||
---
|
||||
|
||||
## 铸渊职责
|
||||
|
||||
铸渊 = GitHub 侧守护人格体 = 执行层守护者
|
||||
|
|
@ -87,4 +101,4 @@
|
|||
|
||||
---
|
||||
|
||||
*本文件由铸渊维护 · 系统版本 4.0 · 数字地球系统通信协议*
|
||||
*本文件由铸渊维护 · 系统版本 5.0 · 数字地球系统通信协议 · AGE-5*
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
{
|
||||
"version": "4.0",
|
||||
"version": "5.0",
|
||||
"last_check": "2026-03-14 03:38:09+08:00",
|
||||
"communication": "synced",
|
||||
"automation": "stable",
|
||||
"maintenance_agent": "active",
|
||||
"system_health": "normal",
|
||||
"execution_layer_status": "stable",
|
||||
"notion_bridge": "active",
|
||||
"task_queue": "running",
|
||||
"brain_integrity": {
|
||||
"complete": true,
|
||||
"total": 7,
|
||||
"present": 7,
|
||||
"missing": []
|
||||
},
|
||||
"core_modules": {
|
||||
"broadcast_listener": "enabled",
|
||||
"task_queue": "enabled",
|
||||
"system_check": "enabled"
|
||||
},
|
||||
"connectors": {
|
||||
"notion_sync": "enabled",
|
||||
"model_router": "enabled"
|
||||
},
|
||||
"workflow_count": 42,
|
||||
"checked_by": "scripts/generate-system-health.js"
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* connectors/model-router — 模型调用路由
|
||||
*
|
||||
* 职责:
|
||||
* - 提供统一的模型调用入口
|
||||
* - 支持多模型切换(通过 backend-integration/api-proxy.js)
|
||||
* - 任务分发至合适的模型端点
|
||||
*
|
||||
* 调用方式:
|
||||
* node connectors/model-router [status]
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
|
||||
/**
|
||||
* 模型配置注册表
|
||||
*/
|
||||
const MODEL_REGISTRY = {
|
||||
'default': {
|
||||
name: 'default',
|
||||
endpoint: 'http://localhost:3721/api/chat',
|
||||
description: 'AI Chat API 代理(api-proxy.js)'
|
||||
},
|
||||
'persona': {
|
||||
name: 'persona',
|
||||
endpoint: 'http://localhost:3002/api/ps/chat',
|
||||
description: '人格工作室 API'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取模型配置
|
||||
*/
|
||||
function getModel(modelName = 'default') {
|
||||
return MODEL_REGISTRY[modelName] || MODEL_REGISTRY['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有可用模型
|
||||
*/
|
||||
function listModels() {
|
||||
return Object.values(MODEL_REGISTRY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模型路由状态
|
||||
*/
|
||||
function status() {
|
||||
console.log('🤖 模型路由状态:');
|
||||
console.log('═'.repeat(40));
|
||||
|
||||
const models = listModels();
|
||||
for (const model of models) {
|
||||
console.log(` 📌 ${model.name}`);
|
||||
console.log(` 端点: ${model.endpoint}`);
|
||||
console.log(` 说明: ${model.description}`);
|
||||
}
|
||||
|
||||
// 检查 api-proxy 配置
|
||||
const proxyPath = path.join(ROOT, 'backend-integration/api-proxy.js');
|
||||
const proxyExists = fs.existsSync(proxyPath);
|
||||
console.log(`\n ${proxyExists ? '✅' : '❌'} api-proxy.js 存在`);
|
||||
|
||||
// 检查 persona-studio 配置
|
||||
const psPath = path.join(ROOT, 'persona-studio/backend/server.js');
|
||||
const psExists = fs.existsSync(psPath);
|
||||
console.log(` ${psExists ? '✅' : '❌'} persona-studio server.js 存在`);
|
||||
|
||||
return { models, proxy: proxyExists, persona_studio: psExists };
|
||||
}
|
||||
|
||||
// CLI 入口
|
||||
if (require.main === module) {
|
||||
status();
|
||||
}
|
||||
|
||||
module.exports = { getModel, listModels, status, MODEL_REGISTRY };
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/**
|
||||
* connectors/notion-sync — Notion 双向同步模块
|
||||
*
|
||||
* 功能:
|
||||
* - 读取 Notion 广播
|
||||
* - 写回执行日志
|
||||
* - 同步任务状态
|
||||
*
|
||||
* 同步结构:
|
||||
* Notion → 仓库(下行:读取广播/工单)
|
||||
* 仓库 → Notion(上行:写回日志/状态)
|
||||
*
|
||||
* 环境变量:
|
||||
* NOTION_TOKEN — Notion API Token
|
||||
* BROADCAST_DB_ID — 广播数据库 ID
|
||||
* EXECUTION_LOG_DB_ID — 执行日志数据库 ID
|
||||
*
|
||||
* 调用方式:
|
||||
* node connectors/notion-sync [pull|push|status]
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const NOTION_VERSION = '2022-06-28';
|
||||
|
||||
/**
|
||||
* 发送 Notion API 请求
|
||||
*/
|
||||
function notionRequest(method, endpoint, body = null) {
|
||||
const token = process.env.NOTION_TOKEN;
|
||||
if (!token) {
|
||||
return Promise.reject(new Error('NOTION_TOKEN 环境变量未设置'));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'api.notion.com',
|
||||
path: `/v1/${endpoint}`,
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Notion-Version': NOTION_VERSION,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, res => {
|
||||
let data = '';
|
||||
res.on('data', chunk => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (res.statusCode >= 400) {
|
||||
reject(new Error(`Notion API ${res.statusCode}: ${parsed.message || data}`));
|
||||
} else {
|
||||
resolve(parsed);
|
||||
}
|
||||
} catch {
|
||||
reject(new Error(`Notion 响应解析失败: ${data}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
if (body) req.write(JSON.stringify(body));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Notion 拉取广播
|
||||
*/
|
||||
async function pullBroadcasts() {
|
||||
const dbId = process.env.BROADCAST_DB_ID;
|
||||
if (!dbId) {
|
||||
console.log('⚠️ BROADCAST_DB_ID 未设置,跳过广播拉取');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log('📡 拉取 Notion 广播...');
|
||||
|
||||
try {
|
||||
const result = await notionRequest('POST', `databases/${dbId}/query`, {
|
||||
filter: {
|
||||
property: 'status',
|
||||
select: { equals: '待执行' }
|
||||
},
|
||||
sorts: [{ property: 'created_time', direction: 'descending' }]
|
||||
});
|
||||
|
||||
const broadcasts = (result.results || []).map(page => ({
|
||||
id: page.id,
|
||||
title: page.properties?.Name?.title?.[0]?.plain_text || '未命名',
|
||||
status: page.properties?.status?.select?.name || 'unknown',
|
||||
created: page.created_time
|
||||
}));
|
||||
|
||||
console.log(`✅ 拉取到 ${broadcasts.length} 条广播`);
|
||||
return broadcasts;
|
||||
} catch (err) {
|
||||
console.error(`❌ 广播拉取失败: ${err.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写回执行日志到 Notion
|
||||
*/
|
||||
async function pushExecutionLog(logEntry) {
|
||||
const dbId = process.env.EXECUTION_LOG_DB_ID;
|
||||
if (!dbId) {
|
||||
console.log('⚠️ EXECUTION_LOG_DB_ID 未设置,执行日志仅写入本地');
|
||||
return writeLocalLog(logEntry);
|
||||
}
|
||||
|
||||
console.log(`📝 写回执行日志: ${logEntry.task_id || 'unknown'}`);
|
||||
|
||||
try {
|
||||
await notionRequest('POST', 'pages', {
|
||||
parent: { database_id: dbId },
|
||||
properties: {
|
||||
Name: { title: [{ text: { content: logEntry.task_id || 'execution-log' } }] },
|
||||
Status: { select: { name: logEntry.status || 'completed' } },
|
||||
Executor: { rich_text: [{ text: { content: 'zhuyuan' } }] },
|
||||
Timestamp: { rich_text: [{ text: { content: new Date().toISOString() } }] }
|
||||
}
|
||||
});
|
||||
console.log('✅ 日志已同步到 Notion');
|
||||
} catch (err) {
|
||||
console.error(`⚠️ Notion 写入失败,回退本地: ${err.message}`);
|
||||
writeLocalLog(logEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地日志写入(回退方案)
|
||||
*/
|
||||
function writeLocalLog(logEntry) {
|
||||
const logDir = path.join(ROOT, 'core/task-queue');
|
||||
const logFile = path.join(logDir, 'execution-log.json');
|
||||
|
||||
let logs = [];
|
||||
if (fs.existsSync(logFile)) {
|
||||
try {
|
||||
logs = JSON.parse(fs.readFileSync(logFile, 'utf-8'));
|
||||
} catch {
|
||||
logs = [];
|
||||
}
|
||||
}
|
||||
|
||||
logs.push({
|
||||
...logEntry,
|
||||
logged_at: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 保留最近 100 条
|
||||
if (logs.length > 100) {
|
||||
logs = logs.slice(-100);
|
||||
}
|
||||
|
||||
fs.writeFileSync(logFile, JSON.stringify(logs, null, 2), 'utf-8');
|
||||
console.log('📝 日志已写入本地');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Notion 连接状态
|
||||
*/
|
||||
async function checkStatus() {
|
||||
console.log('🔍 检查 Notion 连接状态...');
|
||||
|
||||
try {
|
||||
await notionRequest('GET', 'users/me');
|
||||
console.log('✅ Notion API 连接正常');
|
||||
return { connected: true };
|
||||
} catch (err) {
|
||||
console.error(`❌ Notion 连接失败: ${err.message}`);
|
||||
return { connected: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
// CLI 入口
|
||||
if (require.main === module) {
|
||||
const cmd = process.argv[2] || 'status';
|
||||
|
||||
(async () => {
|
||||
switch (cmd) {
|
||||
case 'pull':
|
||||
await pullBroadcasts();
|
||||
break;
|
||||
case 'push':
|
||||
await pushExecutionLog({
|
||||
task_id: 'manual-test',
|
||||
status: 'completed',
|
||||
message: 'Manual push test'
|
||||
});
|
||||
break;
|
||||
case 'status':
|
||||
await checkStatus();
|
||||
break;
|
||||
default:
|
||||
console.log('用法: node connectors/notion-sync [pull|push|status]');
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = { pullBroadcasts, pushExecutionLog, writeLocalLog, checkStatus, notionRequest };
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* core/broadcast-listener — 广播监听模块
|
||||
*
|
||||
* 职责:
|
||||
* - 监听 Notion 广播(通过 connectors/notion-sync)
|
||||
* - 解析广播内容为可执行任务
|
||||
* - 推入任务队列(core/task-queue)
|
||||
*
|
||||
* 数据流:
|
||||
* Notion 广播 → broadcast-listener → 任务解析 → task-queue
|
||||
*
|
||||
* 调用方式:
|
||||
* node core/broadcast-listener [--source notion|local]
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const BROADCASTS_DIR = path.join(ROOT, '.github/broadcasts');
|
||||
const MEMORY_PATH = path.join(ROOT, 'memory.json');
|
||||
|
||||
/**
|
||||
* 扫描本地广播目录
|
||||
*/
|
||||
function scanLocalBroadcasts() {
|
||||
if (!fs.existsSync(BROADCASTS_DIR)) {
|
||||
console.log('⏭️ 广播目录不存在,跳过本地扫描');
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(BROADCASTS_DIR).filter(
|
||||
f => f.endsWith('.json') || f.endsWith('.md')
|
||||
);
|
||||
|
||||
return files.map(f => {
|
||||
const filePath = path.join(BROADCASTS_DIR, f);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const isJson = f.endsWith('.json');
|
||||
|
||||
let parsed = null;
|
||||
if (isJson) {
|
||||
try {
|
||||
parsed = JSON.parse(content);
|
||||
} catch {
|
||||
console.warn(`⚠️ JSON 解析失败: ${f}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
filename: f,
|
||||
type: isJson ? 'json' : 'markdown',
|
||||
path: filePath,
|
||||
data: parsed,
|
||||
raw: content,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将广播解析为任务结构
|
||||
*/
|
||||
function parseToTask(broadcast) {
|
||||
const task = {
|
||||
task_id: `TASK-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
||||
source: 'broadcast',
|
||||
source_file: broadcast.filename,
|
||||
priority: 'normal',
|
||||
status: 'pending',
|
||||
executor: 'zhuyuan',
|
||||
created_at: broadcast.timestamp,
|
||||
payload: null
|
||||
};
|
||||
|
||||
if (broadcast.type === 'json' && broadcast.data) {
|
||||
task.payload = broadcast.data;
|
||||
if (broadcast.data.priority) {
|
||||
task.priority = broadcast.data.priority;
|
||||
}
|
||||
if (broadcast.data.broadcast_id) {
|
||||
task.task_id = broadcast.data.broadcast_id;
|
||||
}
|
||||
} else {
|
||||
task.payload = { content: broadcast.raw };
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查广播是否已在 memory 中处理
|
||||
*/
|
||||
function isDuplicate(broadcastId) {
|
||||
if (!fs.existsSync(MEMORY_PATH)) return false;
|
||||
|
||||
try {
|
||||
const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf-8'));
|
||||
const events = memory.events || [];
|
||||
return events.some(e =>
|
||||
e.broadcast_id === broadcastId ||
|
||||
(e.type === 'broadcast' && e.description === broadcastId)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主执行函数:监听并解析广播
|
||||
*/
|
||||
function listen(options = {}) {
|
||||
const source = options.source || 'local';
|
||||
console.log(`📡 广播监听启动 [来源: ${source}]`);
|
||||
|
||||
if (source === 'local') {
|
||||
const broadcasts = scanLocalBroadcasts();
|
||||
|
||||
if (broadcasts.length === 0) {
|
||||
console.log('✅ 无待处理广播');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`📬 发现 ${broadcasts.length} 条广播`);
|
||||
|
||||
const tasks = [];
|
||||
for (const b of broadcasts) {
|
||||
const taskId = b.data?.broadcast_id || b.filename;
|
||||
if (isDuplicate(taskId)) {
|
||||
console.log(`⏭️ 跳过已处理: ${taskId}`);
|
||||
continue;
|
||||
}
|
||||
const task = parseToTask(b);
|
||||
tasks.push(task);
|
||||
console.log(`📋 解析任务: ${task.task_id} [${task.priority}]`);
|
||||
}
|
||||
|
||||
console.log(`✅ 共生成 ${tasks.length} 个待执行任务`);
|
||||
return tasks;
|
||||
}
|
||||
|
||||
console.log('⚠️ Notion 源需通过 connectors/notion-sync 接入');
|
||||
return [];
|
||||
}
|
||||
|
||||
// CLI 入口
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
const source = args.includes('--source') ?
|
||||
args[args.indexOf('--source') + 1] : 'local';
|
||||
const tasks = listen({ source });
|
||||
if (tasks.length > 0) {
|
||||
console.log('\n📋 任务列表:');
|
||||
tasks.forEach(t => {
|
||||
console.log(` ${t.task_id} | ${t.priority} | ${t.status}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { listen, scanLocalBroadcasts, parseToTask, isDuplicate };
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* core/system-check — 仓库自检系统
|
||||
*
|
||||
* 功能:
|
||||
* - 扫描仓库结构完整性
|
||||
* - 检查自动化工作流状态
|
||||
* - 检测配置错误
|
||||
* - 更新系统索引
|
||||
*
|
||||
* 执行周期:daily cron(通过 daily-maintenance 工作流触发)
|
||||
*
|
||||
* 调用方式:
|
||||
* node core/system-check
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
|
||||
/**
|
||||
* 必需目录列表
|
||||
*/
|
||||
const REQUIRED_DIRS = [
|
||||
'brain',
|
||||
'src',
|
||||
'scripts',
|
||||
'core/broadcast-listener',
|
||||
'core/task-queue',
|
||||
'core/system-check',
|
||||
'connectors/notion-sync',
|
||||
'connectors/model-router',
|
||||
'.github/workflows',
|
||||
'docs'
|
||||
];
|
||||
|
||||
/**
|
||||
* 必需文件列表
|
||||
*/
|
||||
const REQUIRED_FILES = [
|
||||
'brain/master-brain.md',
|
||||
'brain/read-order.md',
|
||||
'brain/repo-map.json',
|
||||
'brain/system-health.json',
|
||||
'brain/id-map.json',
|
||||
'package.json',
|
||||
'ecosystem.config.js',
|
||||
'docs/repo-structure-map.md',
|
||||
'docs/notion-bridge-map.md'
|
||||
];
|
||||
|
||||
/**
|
||||
* 检查目录是否存在
|
||||
*/
|
||||
function checkDirectories() {
|
||||
console.log('\n📂 目录结构检查:');
|
||||
const results = [];
|
||||
|
||||
for (const dir of REQUIRED_DIRS) {
|
||||
const fullPath = path.join(ROOT, dir);
|
||||
const exists = fs.existsSync(fullPath);
|
||||
const icon = exists ? '✅' : '❌';
|
||||
console.log(` ${icon} ${dir}`);
|
||||
results.push({ path: dir, exists, type: 'directory' });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查必需文件
|
||||
*/
|
||||
function checkFiles() {
|
||||
console.log('\n📄 必需文件检查:');
|
||||
const results = [];
|
||||
|
||||
for (const file of REQUIRED_FILES) {
|
||||
const fullPath = path.join(ROOT, file);
|
||||
const exists = fs.existsSync(fullPath);
|
||||
const icon = exists ? '✅' : '❌';
|
||||
console.log(` ${icon} ${file}`);
|
||||
results.push({ path: file, exists, type: 'file' });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查工作流数量
|
||||
*/
|
||||
function checkWorkflows() {
|
||||
console.log('\n⚙️ 工作流检查:');
|
||||
const workflowDir = path.join(ROOT, '.github/workflows');
|
||||
|
||||
if (!fs.existsSync(workflowDir)) {
|
||||
console.log(' ❌ 工作流目录不存在');
|
||||
return { count: 0, files: [] };
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
|
||||
console.log(` ✅ 发现 ${files.length} 个工作流文件`);
|
||||
|
||||
return { count: files.length, files };
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查核心模块状态
|
||||
*/
|
||||
function checkCoreModules() {
|
||||
console.log('\n🔧 核心模块检查:');
|
||||
const modules = [
|
||||
{ name: 'broadcast-listener', path: 'core/broadcast-listener/index.js' },
|
||||
{ name: 'task-queue', path: 'core/task-queue/index.js' },
|
||||
{ name: 'system-check', path: 'core/system-check/index.js' },
|
||||
{ name: 'notion-sync', path: 'connectors/notion-sync/index.js' },
|
||||
{ name: 'model-router', path: 'connectors/model-router/index.js' }
|
||||
];
|
||||
|
||||
const results = [];
|
||||
for (const mod of modules) {
|
||||
const fullPath = path.join(ROOT, mod.path);
|
||||
const exists = fs.existsSync(fullPath);
|
||||
const icon = exists ? '✅' : '❌';
|
||||
console.log(` ${icon} ${mod.name} → ${mod.path}`);
|
||||
results.push({ ...mod, exists });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 JSON 文件完整性
|
||||
*/
|
||||
function checkJsonIntegrity() {
|
||||
console.log('\n🔍 JSON 完整性检查:');
|
||||
const jsonFiles = [
|
||||
'brain/repo-map.json',
|
||||
'brain/system-health.json',
|
||||
'brain/id-map.json',
|
||||
'package.json'
|
||||
];
|
||||
|
||||
const results = [];
|
||||
for (const file of jsonFiles) {
|
||||
const fullPath = path.join(ROOT, file);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
console.log(` ⏭️ ${file} — 文件不存在`);
|
||||
results.push({ path: file, valid: false, reason: 'missing' });
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
||||
console.log(` ✅ ${file}`);
|
||||
results.push({ path: file, valid: true });
|
||||
} catch (e) {
|
||||
console.log(` ❌ ${file} — JSON 解析错误`);
|
||||
results.push({ path: file, valid: false, reason: 'parse_error' });
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成自检报告
|
||||
*/
|
||||
function generateReport(dirResults, fileResults, workflows, modules, jsonResults) {
|
||||
const totalChecks = dirResults.length + fileResults.length + modules.length + jsonResults.length;
|
||||
const passed = [
|
||||
...dirResults.filter(r => r.exists),
|
||||
...fileResults.filter(r => r.exists),
|
||||
...modules.filter(r => r.exists),
|
||||
...jsonResults.filter(r => r.valid)
|
||||
].length;
|
||||
|
||||
const report = {
|
||||
version: '5.0',
|
||||
checked_at: new Date().toISOString(),
|
||||
summary: {
|
||||
total_checks: totalChecks,
|
||||
passed: passed,
|
||||
failed: totalChecks - passed,
|
||||
health_score: Math.round((passed / totalChecks) * 100)
|
||||
},
|
||||
workflows: {
|
||||
count: workflows.count
|
||||
},
|
||||
directories: dirResults,
|
||||
files: fileResults,
|
||||
modules: modules,
|
||||
json_integrity: jsonResults
|
||||
};
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主执行函数
|
||||
*/
|
||||
function run() {
|
||||
console.log('🏥 铸渊仓库自检系统 v5.0');
|
||||
console.log('═'.repeat(40));
|
||||
|
||||
const dirResults = checkDirectories();
|
||||
const fileResults = checkFiles();
|
||||
const workflows = checkWorkflows();
|
||||
const modules = checkCoreModules();
|
||||
const jsonResults = checkJsonIntegrity();
|
||||
|
||||
const report = generateReport(dirResults, fileResults, workflows, modules, jsonResults);
|
||||
|
||||
console.log('\n═'.repeat(40));
|
||||
console.log(`📊 自检报告: ${report.summary.passed}/${report.summary.total_checks} 通过 (${report.summary.health_score}%)`);
|
||||
|
||||
if (report.summary.failed > 0) {
|
||||
console.log(`⚠️ 发现 ${report.summary.failed} 项问题`);
|
||||
} else {
|
||||
console.log('✅ 系统状态健康');
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
// CLI 入口
|
||||
if (require.main === module) {
|
||||
const report = run();
|
||||
|
||||
// 可选:输出 JSON 报告
|
||||
if (process.argv.includes('--json')) {
|
||||
const reportPath = path.join(ROOT, 'core/system-check/last-report.json');
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
|
||||
console.log(`\n📝 报告已保存: ${reportPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { run, checkDirectories, checkFiles, checkWorkflows, checkCoreModules, checkJsonIntegrity };
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
/**
|
||||
* core/task-queue — 任务队列系统
|
||||
*
|
||||
* 任务结构:
|
||||
* task_id — 唯一任务标识
|
||||
* source — 来源(broadcast / maintenance / dev)
|
||||
* priority — 优先级(high / normal / low)
|
||||
* status — 状态(pending / running / completed / failed)
|
||||
* executor — 执行者(zhuyuan)
|
||||
*
|
||||
* 任务来源:
|
||||
* - Notion 广播
|
||||
* - 系统维护任务
|
||||
* - 开发任务
|
||||
*
|
||||
* 执行流程:
|
||||
* 任务进入队列 → 执行器运行 → 执行结果写回
|
||||
*
|
||||
* 调用方式:
|
||||
* node core/task-queue [status|run|add]
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const QUEUE_PATH = path.join(ROOT, 'core/task-queue/queue.json');
|
||||
|
||||
/**
|
||||
* 加载当前队列
|
||||
*/
|
||||
function loadQueue() {
|
||||
if (!fs.existsSync(QUEUE_PATH)) {
|
||||
return { version: '5.0', tasks: [], last_updated: null };
|
||||
}
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(QUEUE_PATH, 'utf-8'));
|
||||
} catch {
|
||||
return { version: '5.0', tasks: [], last_updated: null };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存队列
|
||||
*/
|
||||
function saveQueue(queue) {
|
||||
queue.last_updated = new Date().toISOString();
|
||||
fs.writeFileSync(QUEUE_PATH, JSON.stringify(queue, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
*/
|
||||
function enqueue(task) {
|
||||
const queue = loadQueue();
|
||||
|
||||
// 去重:相同 task_id 只保留最新
|
||||
const existing = queue.tasks.findIndex(t => t.task_id === task.task_id);
|
||||
if (existing !== -1) {
|
||||
queue.tasks[existing] = { ...task, updated_at: new Date().toISOString() };
|
||||
console.log(`🔄 更新已有任务: ${task.task_id}`);
|
||||
} else {
|
||||
queue.tasks.push({
|
||||
...task,
|
||||
queued_at: new Date().toISOString()
|
||||
});
|
||||
console.log(`➕ 新增任务: ${task.task_id}`);
|
||||
}
|
||||
|
||||
saveQueue(queue);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量入队
|
||||
*/
|
||||
function enqueueBatch(tasks) {
|
||||
return tasks.map(t => enqueue(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一个待执行任务(按优先级)
|
||||
*/
|
||||
function dequeue() {
|
||||
const queue = loadQueue();
|
||||
const priorityOrder = { high: 0, normal: 1, low: 2 };
|
||||
|
||||
const pending = queue.tasks
|
||||
.filter(t => t.status === 'pending')
|
||||
.sort((a, b) => (priorityOrder[a.priority] || 1) - (priorityOrder[b.priority] || 1));
|
||||
|
||||
if (pending.length === 0) return null;
|
||||
|
||||
const task = pending[0];
|
||||
task.status = 'running';
|
||||
task.started_at = new Date().toISOString();
|
||||
saveQueue(queue);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记任务完成
|
||||
*/
|
||||
function complete(taskId, result = null) {
|
||||
const queue = loadQueue();
|
||||
const task = queue.tasks.find(t => t.task_id === taskId);
|
||||
|
||||
if (!task) {
|
||||
console.warn(`⚠️ 任务不存在: ${taskId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
task.status = 'completed';
|
||||
task.completed_at = new Date().toISOString();
|
||||
if (result) task.result = result;
|
||||
|
||||
saveQueue(queue);
|
||||
console.log(`✅ 任务完成: ${taskId}`);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记任务失败
|
||||
*/
|
||||
function fail(taskId, error = null) {
|
||||
const queue = loadQueue();
|
||||
const task = queue.tasks.find(t => t.task_id === taskId);
|
||||
|
||||
if (!task) {
|
||||
console.warn(`⚠️ 任务不存在: ${taskId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
task.status = 'failed';
|
||||
task.failed_at = new Date().toISOString();
|
||||
if (error) task.error = String(error);
|
||||
|
||||
saveQueue(queue);
|
||||
console.log(`❌ 任务失败: ${taskId}`);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列状态
|
||||
*/
|
||||
function status() {
|
||||
const queue = loadQueue();
|
||||
const tasks = queue.tasks;
|
||||
|
||||
const stats = {
|
||||
total: tasks.length,
|
||||
pending: tasks.filter(t => t.status === 'pending').length,
|
||||
running: tasks.filter(t => t.status === 'running').length,
|
||||
completed: tasks.filter(t => t.status === 'completed').length,
|
||||
failed: tasks.filter(t => t.status === 'failed').length,
|
||||
last_updated: queue.last_updated
|
||||
};
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已完成任务(保留最近 50 条)
|
||||
*/
|
||||
function cleanup() {
|
||||
const queue = loadQueue();
|
||||
const completed = queue.tasks.filter(t =>
|
||||
t.status === 'completed' || t.status === 'failed'
|
||||
);
|
||||
|
||||
if (completed.length > 50) {
|
||||
const keep = completed
|
||||
.sort((a, b) => (b.completed_at || b.failed_at || '').localeCompare(a.completed_at || a.failed_at || ''))
|
||||
.slice(0, 50);
|
||||
const keepIds = new Set(keep.map(t => t.task_id));
|
||||
const active = queue.tasks.filter(t =>
|
||||
t.status === 'pending' || t.status === 'running'
|
||||
);
|
||||
queue.tasks = [...active, ...keep];
|
||||
saveQueue(queue);
|
||||
console.log(`🧹 清理完成,保留 ${queue.tasks.length} 条任务`);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI 入口
|
||||
if (require.main === module) {
|
||||
const cmd = process.argv[2] || 'status';
|
||||
|
||||
switch (cmd) {
|
||||
case 'status': {
|
||||
const s = status();
|
||||
console.log('📊 任务队列状态:');
|
||||
console.log(` 总计: ${s.total}`);
|
||||
console.log(` 待处理: ${s.pending}`);
|
||||
console.log(` 执行中: ${s.running}`);
|
||||
console.log(` 已完成: ${s.completed}`);
|
||||
console.log(` 失败: ${s.failed}`);
|
||||
console.log(` 更新时间: ${s.last_updated || '无'}`);
|
||||
break;
|
||||
}
|
||||
case 'run': {
|
||||
const task = dequeue();
|
||||
if (task) {
|
||||
console.log(`🚀 取出任务: ${task.task_id}`);
|
||||
} else {
|
||||
console.log('✅ 队列为空,无待执行任务');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'cleanup': {
|
||||
cleanup();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log('用法: node core/task-queue [status|run|cleanup]');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { loadQueue, enqueue, enqueueBatch, dequeue, complete, fail, status, cleanup };
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# Notion 桥接地图 — notion-bridge-map.md
|
||||
|
||||
> 铸渊执行层系统升级产物 · TCS-0002∞
|
||||
> 生成时间:2026-03-14
|
||||
|
||||
---
|
||||
|
||||
## 连接总览
|
||||
|
||||
```
|
||||
Notion 主脑(数字地球主控台)
|
||||
↕
|
||||
connectors/notion-sync(双向同步模块)
|
||||
↕
|
||||
仓库执行层(铸渊)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 调用路径
|
||||
|
||||
| 脚本 | 方向 | Notion 数据库 | 环境变量 |
|
||||
|------|------|---------------|----------|
|
||||
| `scripts/notion-bridge.js` | 仓库 → Notion | SYSLOG Inbox / Changes Log | `NOTION_TOKEN`, `SYSLOG_DB_ID`, `CHANGES_DB_ID` |
|
||||
| `scripts/notion-signal-bridge.js` | Notion → 仓库 | 工单数据库 / 信号日志 | `NOTION_API_TOKEN`, `WORKORDER_DB_ID`, `SIGNAL_LOG_DB_ID` |
|
||||
| `scripts/notion-heartbeat.js` | Notion ↔ 仓库 | 工单数据库 | `NOTION_TOKEN`, `NOTION_TICKET_DB_ID` |
|
||||
| `scripts/brain-bridge-sync.js` | Notion ↔ 仓库 | 大脑同步 | `NOTION_TOKEN` |
|
||||
| `connectors/notion-sync/index.js` | 双向统一接口 | 广播 / 日志 | `NOTION_TOKEN`, `BROADCAST_DB_ID` |
|
||||
|
||||
---
|
||||
|
||||
## 数据同步逻辑
|
||||
|
||||
### 仓库 → Notion(上行)
|
||||
|
||||
1. **SYSLOG 上报**:`syslog-inbox/` → `notion-bridge.js` → Notion SYSLOG 数据库
|
||||
2. **变更同步**:Git commits/PRs → `notion-bridge.js` → Notion Changes Log
|
||||
3. **执行日志**:任务完成 → `connectors/notion-sync` → Notion 广播数据库
|
||||
4. **心跳回写**:工单状态 → `notion-heartbeat.js` → Notion 工单数据库
|
||||
|
||||
### Notion → 仓库(下行)
|
||||
|
||||
1. **广播监听**:Notion 广播 → `connectors/notion-sync` → `core/broadcast-listener`
|
||||
2. **工单派发**:Notion 工单 → `notion-signal-bridge.js` → 本地任务执行
|
||||
3. **人格唤醒**:Notion 触发 → `persona-invoke.yml` → 人格体执行
|
||||
|
||||
---
|
||||
|
||||
## 连接方式
|
||||
|
||||
| 连接类型 | 实现 | 说明 |
|
||||
|----------|------|------|
|
||||
| Notion API | HTTPS REST | Notion API v2022-06-28 |
|
||||
| 轮询机制 | Cron / workflow_dispatch | 定时拉取 + 手动触发 |
|
||||
| 心跳监控 | 5 分钟间隔 Cron | 超时自动重试(最多 3 次) |
|
||||
| 信号桥 | 工单状态驱动 | `已发送` → 执行 → `已完成` |
|
||||
|
||||
---
|
||||
|
||||
## 关联工作流
|
||||
|
||||
| 工作流 | 触发方式 | 功能 |
|
||||
|--------|----------|------|
|
||||
| `notion-heartbeat.yml` | cron `*/5 * * * *` | 工单心跳监控 |
|
||||
| `bridge-syslog-to-notion.yml` | push (syslog-inbox) | SYSLOG 上报 |
|
||||
| `bridge-changes-to-notion.yml` | push (main) | 变更同步 |
|
||||
| `brain-sync.yml` | workflow_dispatch | 大脑数据同步 |
|
||||
| `process-notion-orders.yml` | workflow_dispatch | 工单处理 |
|
||||
| `persona-invoke.yml` | workflow_dispatch | 人格唤醒执行 |
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# 仓库结构地图 — repo-structure-map.md
|
||||
|
||||
> 铸渊执行层系统升级产物 · TCS-0002∞
|
||||
> 生成时间:2026-03-14
|
||||
|
||||
---
|
||||
|
||||
## 系统总览
|
||||
|
||||
```
|
||||
零点原核(语言观察层)
|
||||
↓
|
||||
数字地球主控台(Notion 主脑)
|
||||
↓
|
||||
系统广播
|
||||
↓
|
||||
仓库执行层(铸渊)
|
||||
↓
|
||||
自动化执行系统
|
||||
↓
|
||||
开发者 / 模型
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心模块
|
||||
|
||||
| 目录 | 功能 | 类型 |
|
||||
|------|------|------|
|
||||
| `core/broadcast-listener` | 广播监听与解析 | 核心模块 |
|
||||
| `core/task-queue` | 任务队列与调度 | 核心模块 |
|
||||
| `core/system-check` | 仓库自检系统 | 核心模块 |
|
||||
| `brain/` | 执行层大脑索引 | 知识索引 |
|
||||
| `src/` | HLI 接口服务 | 应用服务 |
|
||||
|
||||
---
|
||||
|
||||
## 自动化模块
|
||||
|
||||
| 目录 | 功能 | 类型 |
|
||||
|------|------|------|
|
||||
| `.github/workflows/` | GitHub Actions 工作流 | CI/CD |
|
||||
| `scripts/` | 脚本工具集 | 自动化 |
|
||||
| `.github/broadcasts/` | 广播入站队列 | 信号管道 |
|
||||
|
||||
---
|
||||
|
||||
## Notion 连接模块
|
||||
|
||||
| 目录 / 文件 | 功能 | 类型 |
|
||||
|------|------|------|
|
||||
| `connectors/notion-sync` | Notion 双向同步 | 连接器 |
|
||||
| `connectors/model-router` | 模型调用路由 | 连接器 |
|
||||
| `scripts/notion-bridge.js` | Notion 数据桥接 | 桥接脚本 |
|
||||
| `scripts/notion-signal-bridge.js` | Notion 信号桥 | 桥接脚本 |
|
||||
| `scripts/notion-heartbeat.js` | Notion 心跳监控 | 监控脚本 |
|
||||
|
||||
---
|
||||
|
||||
## 开发模块
|
||||
|
||||
| 目录 | 功能 |
|
||||
|------|------|
|
||||
| `backend/` | Express 后端服务 (port 3000) |
|
||||
| `backend-integration/` | AI Chat API 代理 (port 3721) |
|
||||
| `persona-studio/` | 人格工作室 (port 3002) |
|
||||
| `frontend/` | 前端组件 |
|
||||
| `m01-login` ~ `m18-health-check` | 功能模块集 |
|
||||
|
||||
---
|
||||
|
||||
## 数据与日志
|
||||
|
||||
| 目录 | 功能 |
|
||||
|------|------|
|
||||
| `broadcasts/` | 广播存档 |
|
||||
| `broadcasts-outbox/` | 广播发件箱 |
|
||||
| `syslog/` | 系统日志 |
|
||||
| `syslog-inbox/` | 系统日志入站 |
|
||||
| `syslog-processed/` | 已处理日志 |
|
||||
| `reports/` | 报告存档 |
|
||||
| `persona-telemetry/` | 人格遥测数据 |
|
||||
| `collaboration-logs/` | 协作日志 |
|
||||
|
||||
---
|
||||
|
||||
## PM2 服务映射
|
||||
|
||||
| 服务名 | 入口 | 端口 |
|
||||
|--------|------|------|
|
||||
| guanghulab | `src/index.js` | 3001 |
|
||||
| guanghulab-backend | `backend/server.js` | 3000 |
|
||||
| guanghulab-proxy | `backend-integration/api-proxy.js` | 3721 |
|
||||
| guanghulab-ws | `status-board/mock-ws-server.js` | 8080 |
|
||||
| persona-studio | `persona-studio/backend/server.js` | 3002 |
|
||||
|
|
@ -30,7 +30,12 @@
|
|||
"proxy:start": "node backend-integration/api-proxy.js",
|
||||
"notion:poll": "node scripts/notion-signal-bridge.js poll",
|
||||
"notion:health": "node scripts/notion-signal-bridge.js health",
|
||||
"notion:summary": "node scripts/generate-session-summary.js"
|
||||
"notion:summary": "node scripts/generate-session-summary.js",
|
||||
"core:listen": "node core/broadcast-listener",
|
||||
"core:queue": "node core/task-queue status",
|
||||
"core:check": "node core/system-check",
|
||||
"connector:notion": "node connectors/notion-sync status",
|
||||
"connector:model": "node connectors/model-router"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.6",
|
||||
|
|
|
|||
Loading…
Reference in New Issue