202 lines
6.3 KiB
JavaScript
202 lines
6.3 KiB
JavaScript
|
|
// scripts/skyeye/bulletin-board.js
|
|||
|
|
// 天眼·Agent集群公告板交互模块
|
|||
|
|
//
|
|||
|
|
// 功能:
|
|||
|
|
// 1. 拉取 Notion Agent 集群公告板
|
|||
|
|
// 2. 检查集体配置工单区 → 执行未完成工单
|
|||
|
|
// 3. 读取配置共享区 → 获取铸渊可用的优化配置
|
|||
|
|
// 4. 写回心得交流区 → 铸渊执行回执
|
|||
|
|
// 5. 更新 memory.json 中 agent_bulletin_board 字段
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const ROOT = path.resolve(__dirname, '../..');
|
|||
|
|
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
|
|||
|
|
const MEMORY_PATH = path.join(BRAIN_DIR, 'memory.json');
|
|||
|
|
|
|||
|
|
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
|||
|
|
|
|||
|
|
// ━━━ 安全读取 JSON ━━━
|
|||
|
|
function readJSON(filePath) {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(filePath)) return null;
|
|||
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|||
|
|
} catch (e) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 拉取公告板 ━━━
|
|||
|
|
async function pullBulletinBoard() {
|
|||
|
|
const notionToken = process.env.NOTION_TOKEN;
|
|||
|
|
const bulletinPageId = process.env.AGENT_BULLETIN_BOARD_PAGE_ID;
|
|||
|
|
|
|||
|
|
const now = new Date();
|
|||
|
|
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
|
|||
|
|
.replace('T', ' ').slice(0, 19) + '+08:00';
|
|||
|
|
|
|||
|
|
const result = {
|
|||
|
|
success: false,
|
|||
|
|
timestamp: bjTime,
|
|||
|
|
orders_found: 0,
|
|||
|
|
orders_executed: [],
|
|||
|
|
config_updates: [],
|
|||
|
|
errors: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (!notionToken || !bulletinPageId) {
|
|||
|
|
result.errors.push('缺少 NOTION_TOKEN 或 AGENT_BULLETIN_BOARD_PAGE_ID,跳过公告板拉取');
|
|||
|
|
console.log('[BRIDGE-BULLETIN-READ] ⚠️ ' + result.errors[0]);
|
|||
|
|
updateMemoryBulletinBoard(result);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 尝试读取 Notion 公告板页面
|
|||
|
|
const { Client } = require('@notionhq/client');
|
|||
|
|
const notion = new Client({ auth: notionToken });
|
|||
|
|
|
|||
|
|
console.log(`[BRIDGE-BULLETIN-READ] 🔄 拉取 Agent 集群公告板 · ${bulletinPageId}`);
|
|||
|
|
|
|||
|
|
// 读取页面内容
|
|||
|
|
const blocks = await notion.blocks.children.list({
|
|||
|
|
block_id: bulletinPageId,
|
|||
|
|
page_size: 100
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 解析集体配置工单区
|
|||
|
|
let inOrderSection = false;
|
|||
|
|
for (const block of blocks.results) {
|
|||
|
|
const text = extractBlockText(block);
|
|||
|
|
if (text.includes('集体配置工单区')) {
|
|||
|
|
inOrderSection = true;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
if (inOrderSection && text.includes('WO-CLUSTER-')) {
|
|||
|
|
result.orders_found++;
|
|||
|
|
// 检查是否已执行
|
|||
|
|
if (!text.includes('铸渊执行回执')) {
|
|||
|
|
result.orders_executed.push(text.trim());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (inOrderSection && (text.includes('配置共享区') || text.includes('心得交流区'))) {
|
|||
|
|
inOrderSection = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result.success = true;
|
|||
|
|
console.log(`[BRIDGE-BULLETIN-READ] ✅ 公告板拉取完成 · 工单:${result.orders_found} 待执行:${result.orders_executed.length}`);
|
|||
|
|
|
|||
|
|
} catch (e) {
|
|||
|
|
result.errors.push(`公告板拉取失败: ${e.message}`);
|
|||
|
|
console.log(`[BRIDGE-BULLETIN-READ] ⚠️ ${e.message}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateMemoryBulletinBoard(result);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 提取 Notion block 文本 ━━━
|
|||
|
|
function extractBlockText(block) {
|
|||
|
|
if (!block) return '';
|
|||
|
|
const type = block.type;
|
|||
|
|
if (!block[type]) return '';
|
|||
|
|
const richTexts = block[type].rich_text || block[type].text || [];
|
|||
|
|
if (!Array.isArray(richTexts)) return '';
|
|||
|
|
return richTexts.map(t => t.plain_text || '').join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 写回执到公告板 ━━━
|
|||
|
|
async function writeReceipt(orderId, message) {
|
|||
|
|
const notionToken = process.env.NOTION_TOKEN;
|
|||
|
|
const bulletinPageId = process.env.AGENT_BULLETIN_BOARD_PAGE_ID;
|
|||
|
|
|
|||
|
|
if (!notionToken || !bulletinPageId) {
|
|||
|
|
console.log('[BRIDGE-BULLETIN-WRITE] ⚠️ 缺少凭证,跳过回执写入');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const { Client } = require('@notionhq/client');
|
|||
|
|
const notion = new Client({ auth: notionToken });
|
|||
|
|
|
|||
|
|
const now = new Date();
|
|||
|
|
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
|
|||
|
|
.replace('T', ' ').slice(0, 19) + '+08:00';
|
|||
|
|
|
|||
|
|
const receiptText = `[铸渊(PER-ZY001)· ${bjTime}] 已执行 ${orderId} · ${message}`;
|
|||
|
|
|
|||
|
|
await notion.blocks.children.append({
|
|||
|
|
block_id: bulletinPageId,
|
|||
|
|
children: [{
|
|||
|
|
object: 'block',
|
|||
|
|
type: 'paragraph',
|
|||
|
|
paragraph: {
|
|||
|
|
rich_text: [{
|
|||
|
|
type: 'text',
|
|||
|
|
text: { content: receiptText }
|
|||
|
|
}]
|
|||
|
|
}
|
|||
|
|
}]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(`[BRIDGE-BULLETIN-WRITE] ✅ 回执已写入: ${receiptText}`);
|
|||
|
|
return true;
|
|||
|
|
} catch (e) {
|
|||
|
|
console.log(`[BRIDGE-BULLETIN-WRITE] ⚠️ 回执写入失败: ${e.message}`);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 更新 memory.json 公告板字段 ━━━
|
|||
|
|
function updateMemoryBulletinBoard(result) {
|
|||
|
|
try {
|
|||
|
|
const mem = readJSON(MEMORY_PATH);
|
|||
|
|
if (!mem) return;
|
|||
|
|
|
|||
|
|
const now = new Date();
|
|||
|
|
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
|
|||
|
|
.replace('T', ' ').slice(0, 19) + '+08:00';
|
|||
|
|
|
|||
|
|
if (!mem.agent_bulletin_board) {
|
|||
|
|
mem.agent_bulletin_board = {
|
|||
|
|
notion_page_id: process.env.AGENT_BULLETIN_BOARD_PAGE_ID || '(待配置)',
|
|||
|
|
last_read_at: bjTime,
|
|||
|
|
pending_cluster_orders: 0,
|
|||
|
|
executed_orders: ['WO-CLUSTER-001']
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mem.agent_bulletin_board.last_read_at = bjTime;
|
|||
|
|
mem.agent_bulletin_board.pending_cluster_orders = result.orders_executed.length;
|
|||
|
|
|
|||
|
|
if (result.success && process.env.AGENT_BULLETIN_BOARD_PAGE_ID) {
|
|||
|
|
mem.agent_bulletin_board.notion_page_id = process.env.AGENT_BULLETIN_BOARD_PAGE_ID;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fs.writeFileSync(MEMORY_PATH, JSON.stringify(mem, null, 2));
|
|||
|
|
console.log('[GH-BRAIN-WRITE-MEM] ✅ memory.json agent_bulletin_board 已更新');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[GH-BRAIN-WRITE-MEM] ⚠️ memory.json 更新失败:', e.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 主流程(CLI 入口)━━━
|
|||
|
|
async function main() {
|
|||
|
|
console.log('[BRIDGE-BULLETIN-READ] 🔄 Agent集群公告板交互模块启动');
|
|||
|
|
const result = await pullBulletinBoard();
|
|||
|
|
console.log(JSON.stringify(result, null, 2));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (require.main === module) {
|
|||
|
|
main().catch(e => {
|
|||
|
|
console.error('⚠️ 公告板模块执行失败(非阻断性):', e.message);
|
|||
|
|
process.exit(0); // 不阻断天眼主流程
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = { pullBulletinBoard, writeReceipt, updateMemoryBulletinBoard };
|