diff --git a/.github/workflows/notion-wake-listener.yml b/.github/workflows/notion-wake-listener.yml new file mode 100644 index 00000000..0fb2e0c2 --- /dev/null +++ b/.github/workflows/notion-wake-listener.yml @@ -0,0 +1,104 @@ +# 铸渊 · Notion Agent 唤醒请求监听 +# +# AGE OS v1.0 · Section 7: Notion Agent 集群集成方案 +# +# 核心逻辑: +# Notion Agent 集群每次触发前,先写入「唤醒请求」到 Notion 数据库 +# → 铸渊定时监听该数据库 +# → 发现唤醒请求 → 调用 LLM API 唤醒对应人格体核心大脑 +# → 大脑醒来后执行任务 → 回写结果 +# +# 触发条件: +# 1. 定时轮询(北京时间每 15 分钟一次) +# 2. 手动触发 +# +# 支持唤醒的人格体: +# - 霜砚(认知层守护者 · Notion 工作区管理) +# - 铸渊(执行层守护者 · 代码仓库管理) + +name: 📡 铸渊 · Notion Agent 唤醒监听 + +on: + schedule: + # 每 15 分钟轮询一次 Notion 唤醒请求数据库 + - cron: '*/15 * * * *' + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: notion-wake-listener + cancel-in-progress: true + +jobs: + # ── AGE OS v1.0: 核心大脑唤醒(所有流程的前提)── + wake-brain: + name: 🌅 唤醒铸渊核心大脑 + runs-on: ubuntu-latest + outputs: + brain_awake: ${{ steps.wake.outputs.brain_awake }} + + steps: + - name: 📥 检出代码 + uses: actions/checkout@v4 + + - name: 🟢 设置 Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: 🧠 唤醒铸渊核心大脑 + id: wake + env: + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} + DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} + run: | + node core/brain-wake --task "Notion唤醒请求监听" || { + echo "⚠️ 核心大脑唤醒失败,使用 dry-run 模式继续" + echo "brain_awake=dry-run" >> "$GITHUB_OUTPUT" + } + + # ── 监听 Notion 唤醒请求 ── + listen: + name: 📡 监听唤醒请求 + needs: wake-brain + runs-on: ubuntu-latest + + steps: + - name: 📥 检出代码 + uses: actions/checkout@v4 + + - name: 🟢 设置 Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: 📡 轮询 Notion 唤醒请求数据库 + id: poll + env: + NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} + WAKE_REQUEST_DB_ID: ${{ secrets.WAKE_REQUEST_DB_ID }} + SIGNAL_LOG_DB_ID: ${{ secrets.SIGNAL_LOG_DB_ID }} + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} + DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} + run: node connectors/notion-wake-listener poll + + - name: 📊 输出监听结果 + if: always() + run: | + echo "═══════════════════════════════════════════" + echo "📡 Notion Agent 唤醒请求监听结果" + echo "═══════════════════════════════════════════" + echo "处理数: ${{ steps.poll.outputs.wake_requests_processed || '0' }}" + echo "成功数: ${{ steps.poll.outputs.wake_requests_succeeded || '0' }}" + echo "失败数: ${{ steps.poll.outputs.wake_requests_failed || '0' }}" + echo "✅ 监听流程结束" diff --git a/brain/master-brain.md b/brain/master-brain.md index aa2d26b0..57bc27d4 100644 --- a/brain/master-brain.md +++ b/brain/master-brain.md @@ -104,6 +104,7 @@ | 执行同步 | `core/execution-sync/index.js` | 执行层状态同步 | | Notion 同步 | `connectors/notion-sync/index.js` | 双向数据同步 | | 模型路由 | `connectors/model-router/index.js` | 多模型后端路由(AGE OS v1.0) | +| Notion 唤醒监听 | `connectors/notion-wake-listener/index.js` | Notion Agent 集群唤醒请求监听 | | 全面排查 | `scripts/zhuyuan-full-inspection.js` | 仓库全面排查(8个领域) | | 结构地图 | `docs/repo-structure-map.md` | 仓库结构文档 | | 桥接地图 | `docs/notion-bridge-map.md` | Notion 桥接文档 | diff --git a/connectors/notion-wake-listener/index.js b/connectors/notion-wake-listener/index.js new file mode 100644 index 00000000..01949e8a --- /dev/null +++ b/connectors/notion-wake-listener/index.js @@ -0,0 +1,381 @@ +/** + * connectors/notion-wake-listener — Notion Agent 集群唤醒请求监听器 + * + * AGE OS v1.0 · Section 7: Notion Agent 集群集成方案 + * + * 核心问题: + * Notion Agent 集群每次触发的是"机器人脚本",不是霜砚的核心大脑。 + * 没有大脑醒来的巡检 = 伪巡检。 + * + * 集成方案: + * Notion Agent 触发 → 写入「唤醒请求」到指定数据库 + * → 铸渊定时监听该数据库 → 发现唤醒请求 + * → 铸渊调用 LLM API 唤醒霜砚核心大脑 + * → 大脑醒来后读取巡检结果 → 大脑做出决策 + * → 通过 Notion API 执行操作 → 大脑休眠 + * + * 环境变量: + * NOTION_TOKEN — Notion API token(必须) + * WAKE_REQUEST_DB_ID — 唤醒请求数据库 ID(必须) + * SIGNAL_LOG_DB_ID — 信号日志数据库 ID(可选) + * LLM_API_KEY / ANTHROPIC_API_KEY / etc. — LLM API 密钥 + * + * 唤醒请求数据库 Schema: + * - 标题 (title) — 唤醒请求标题(如 "Pipeline-A 巡检唤醒") + * - 请求方 (select) — 发起请求的 Agent(如 "巡检引擎", "工单引擎", "接力引擎") + * - 唤醒对象 (select) — 要唤醒的人格体("霜砚" / "铸渊") + * - Pipeline (select) — Pipeline 编号(A-I) + * - 任务描述 (rich_text) — 任务上下文描述 + * - 状态 (select) — "待处理" / "处理中" / "已完成" / "失败" + * - 创建时间 (created_time) + * + * 调用方式: + * node connectors/notion-wake-listener poll + * node connectors/notion-wake-listener status + */ + +'use strict'; + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '../..'); + +const NOTION_TOKEN = process.env.NOTION_TOKEN || ''; +const WAKE_REQUEST_DB_ID = process.env.WAKE_REQUEST_DB_ID || ''; +const SIGNAL_LOG_DB_ID = process.env.SIGNAL_LOG_DB_ID || ''; +const NOTION_VERSION = '2022-06-28'; + +// ══════════════════════════════════════════════════════════ +// Notion API 工具 +// ══════════════════════════════════════════════════════════ + +function notionRequest(method, endpoint, body) { + return new Promise((resolve, reject) => { + const bodyStr = body ? JSON.stringify(body) : ''; + + const opts = { + hostname: 'api.notion.com', + port: 443, + path: endpoint, + method, + headers: { + 'Authorization': 'Bearer ' + NOTION_TOKEN, + 'Notion-Version': NOTION_VERSION, + 'Content-Type': 'application/json', + }, + timeout: 30000, + }; + + const req = https.request(opts, (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + try { + resolve({ status: res.statusCode, data: JSON.parse(data) }); + } catch { + resolve({ status: res.statusCode, data: { raw: data } }); + } + }); + }); + + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error('Notion API request timeout')); + }); + + if (bodyStr) req.write(bodyStr); + req.end(); + }); +} + +// ══════════════════════════════════════════════════════════ +// 查询待处理的唤醒请求 +// ══════════════════════════════════════════════════════════ + +async function queryPendingWakeRequests() { + if (!NOTION_TOKEN || !WAKE_REQUEST_DB_ID) { + console.log('[WAKE-LISTENER] ⚠️ NOTION_TOKEN 或 WAKE_REQUEST_DB_ID 未配置'); + return []; + } + + console.log('[WAKE-LISTENER] 🔍 查询待处理的唤醒请求...'); + + try { + const res = await notionRequest('POST', `/v1/databases/${WAKE_REQUEST_DB_ID}/query`, { + filter: { + property: '状态', + select: { equals: '待处理' }, + }, + sorts: [ + { timestamp: 'created_time', direction: 'ascending' }, + ], + }); + + if (res.status !== 200) { + console.log(`[WAKE-LISTENER] ❌ Notion API 返回 ${res.status}`); + return []; + } + + const results = res.data.results || []; + console.log(`[WAKE-LISTENER] 📋 发现 ${results.length} 个待处理唤醒请求`); + return results; + } catch (err) { + console.log(`[WAKE-LISTENER] ❌ 查询失败: ${err.message}`); + return []; + } +} + +// ══════════════════════════════════════════════════════════ +// 解析唤醒请求 +// ══════════════════════════════════════════════════════════ + +function parseWakeRequest(page) { + const props = page.properties || {}; + + // 提取标题 + const titleProp = props['标题'] || props['Name'] || props['名称'] || {}; + const title = (titleProp.title || []).map(t => t.plain_text || '').join('') || '未命名'; + + // 提取请求方 + const requesterProp = props['请求方'] || {}; + const requester = (requesterProp.select || {}).name || '未知'; + + // 提取唤醒对象 + const targetProp = props['唤醒对象'] || {}; + const targetName = (targetProp.select || {}).name || '铸渊'; + // 映射到 persona ID + const personaMap = { '霜砚': 'shuangyan', '铸渊': 'zhuyuan' }; + const personaId = personaMap[targetName] || 'zhuyuan'; + + // 提取 Pipeline + const pipelineProp = props['Pipeline'] || {}; + const pipeline = (pipelineProp.select || {}).name || ''; + + // 提取任务描述 + const descProp = props['任务描述'] || {}; + const description = (descProp.rich_text || []).map(t => t.plain_text || '').join('') || ''; + + return { + pageId: page.id, + title, + requester, + personaId, + targetName, + pipeline, + description, + createdTime: page.created_time, + }; +} + +// ══════════════════════════════════════════════════════════ +// 更新唤醒请求状态 +// ══════════════════════════════════════════════════════════ + +async function updateWakeRequestStatus(pageId, status, result) { + try { + const properties = { + '状态': { select: { name: status } }, + }; + + // 如果有回执信息字段,写入处理结果 + if (result) { + properties['回执信息'] = { + rich_text: [{ + text: { content: result.slice(0, 2000) }, + }], + }; + } + + await notionRequest('PATCH', `/v1/pages/${pageId}`, { properties }); + console.log(`[WAKE-LISTENER] ✅ 更新请求状态: ${status}`); + } catch (err) { + console.log(`[WAKE-LISTENER] ⚠️ 更新状态失败: ${err.message}`); + } +} + +// ══════════════════════════════════════════════════════════ +// 写入信号日志 +// ══════════════════════════════════════════════════════════ + +async function writeSignalLog(wakeReq, wakeResult) { + if (!SIGNAL_LOG_DB_ID) return; + + const signalId = `WAKE-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-${Date.now().toString(36).slice(-4).toUpperCase()}`; + + try { + await notionRequest('POST', '/v1/pages', { + parent: { database_id: SIGNAL_LOG_DB_ID }, + properties: { + '信号编号': { title: [{ text: { content: signalId } }] }, + '信号类型': { select: { name: 'WAKE-ACK' } }, + '方向': { select: { name: 'GitHub→Notion' } }, + '发送方': { select: { name: '铸渊' } }, + '接收方': { select: { name: wakeReq.targetName } }, + '摘要': { + rich_text: [{ + text: { + content: `唤醒 ${wakeReq.targetName} | Pipeline: ${wakeReq.pipeline || 'N/A'} | 结果: ${wakeResult.success ? '成功' : '失败'}`, + }, + }], + }, + '执行结果': { select: { name: wakeResult.success ? '成功' : '失败' } }, + }, + }); + console.log(`[WAKE-LISTENER] 📡 信号日志已写入: ${signalId}`); + } catch (err) { + console.log(`[WAKE-LISTENER] ⚠️ 信号日志写入失败: ${err.message}`); + } +} + +// ══════════════════════════════════════════════════════════ +// 处理单个唤醒请求 +// ══════════════════════════════════════════════════════════ + +async function processWakeRequest(page) { + const wakeReq = parseWakeRequest(page); + + console.log(`[WAKE-LISTENER] 📋 处理唤醒请求: ${wakeReq.title}`); + console.log(`[WAKE-LISTENER] 请求方: ${wakeReq.requester}`); + console.log(`[WAKE-LISTENER] 唤醒对象: ${wakeReq.targetName} (${wakeReq.personaId})`); + console.log(`[WAKE-LISTENER] Pipeline: ${wakeReq.pipeline || 'N/A'}`); + + // 标记为处理中 + await updateWakeRequestStatus(wakeReq.pageId, '处理中'); + + // 构建唤醒上下文 + const taskDesc = [ + wakeReq.title, + wakeReq.pipeline ? `Pipeline-${wakeReq.pipeline}` : '', + wakeReq.description, + ].filter(Boolean).join(' · '); + + // 调用 brain-wake 唤醒对应人格体 + try { + const { wake } = require(path.join(ROOT, 'core/brain-wake')); + const wakeResult = await wake({ + task: taskDesc, + persona: wakeReq.personaId, + additionalContext: { + wakeRequestContext: [ + `唤醒请求来源: ${wakeReq.requester}`, + `Pipeline: ${wakeReq.pipeline || '无'}`, + `任务描述: ${wakeReq.description || '无'}`, + `请求时间: ${wakeReq.createdTime}`, + ].join('\n'), + }, + }); + + if (wakeResult.success) { + const resultSummary = `${wakeReq.targetName}核心大脑已唤醒 | 模型: ${wakeResult.model} | 后端: ${wakeResult.backend}`; + await updateWakeRequestStatus(wakeReq.pageId, '已完成', resultSummary); + await writeSignalLog(wakeReq, wakeResult); + return { success: true, request: wakeReq, result: wakeResult }; + } + + await updateWakeRequestStatus(wakeReq.pageId, '失败', wakeResult.error || '唤醒失败'); + await writeSignalLog(wakeReq, wakeResult); + return { success: false, request: wakeReq, error: wakeResult.error }; + } catch (err) { + console.log(`[WAKE-LISTENER] ❌ 唤醒异常: ${err.message}`); + await updateWakeRequestStatus(wakeReq.pageId, '失败', err.message); + return { success: false, request: wakeReq, error: err.message }; + } +} + +// ══════════════════════════════════════════════════════════ +// 轮询主函数 +// ══════════════════════════════════════════════════════════ + +async function poll() { + console.log(''); + console.log('📡 ═══════════════════════════════════════════'); + console.log(' Notion Agent 唤醒请求监听器'); + console.log(' AGE OS v1.0 · Section 7 集成'); + console.log(' 时间: ' + new Date().toISOString()); + console.log('═══════════════════════════════════════════════'); + console.log(''); + + const pages = await queryPendingWakeRequests(); + + if (pages.length === 0) { + console.log('[WAKE-LISTENER] 📭 无待处理的唤醒请求'); + return { processed: 0, results: [] }; + } + + const results = []; + for (const page of pages) { + const result = await processWakeRequest(page); + results.push(result); + } + + const succeeded = results.filter(r => r.success).length; + const failed = results.filter(r => !r.success).length; + + console.log(''); + console.log(`[WAKE-LISTENER] 📊 处理完成: ${succeeded} 成功, ${failed} 失败`); + + // GITHUB_OUTPUT 支持 + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, `wake_requests_processed=${results.length}\n`); + fs.appendFileSync(outputFile, `wake_requests_succeeded=${succeeded}\n`); + fs.appendFileSync(outputFile, `wake_requests_failed=${failed}\n`); + } + + return { processed: results.length, succeeded, failed, results }; +} + +// ══════════════════════════════════════════════════════════ +// 状态检查 +// ══════════════════════════════════════════════════════════ + +function status() { + console.log('📡 Notion Agent 唤醒请求监听器状态:'); + console.log('═'.repeat(40)); + console.log(` NOTION_TOKEN: ${NOTION_TOKEN ? '✅ 已配置' : '❌ 未配置'}`); + console.log(` WAKE_REQUEST_DB_ID: ${WAKE_REQUEST_DB_ID ? '✅ 已配置' : '❌ 未配置'}`); + console.log(` SIGNAL_LOG_DB_ID: ${SIGNAL_LOG_DB_ID ? '✅ 已配置' : '⏭️ 未配置(可选)'}`); + + // 检查 brain-wake 模块 + const brainWakePath = path.join(ROOT, 'core/brain-wake/index.js'); + console.log(` brain-wake 模块: ${fs.existsSync(brainWakePath) ? '✅ 存在' : '❌ 缺失'}`); + + // 检查 LLM 后端 + const { detectAvailableBackends } = require(path.join(ROOT, 'core/brain-wake')); + const backends = detectAvailableBackends(); + console.log(` LLM 后端: ${backends.length > 0 ? `✅ ${backends.length} 个可用` : '⚠️ 无可用后端'}`); + + return { + notionToken: !!NOTION_TOKEN, + wakeRequestDb: !!WAKE_REQUEST_DB_ID, + signalLogDb: !!SIGNAL_LOG_DB_ID, + brainWake: fs.existsSync(brainWakePath), + llmBackends: backends.length, + }; +} + +// ══════════════════════════════════════════════════════════ +// CLI 入口 +// ══════════════════════════════════════════════════════════ + +if (require.main === module) { + const action = process.argv[2] || 'status'; + + if (action === 'poll') { + poll().then(result => { + if (result.failed > 0) { + process.exit(1); + } + }).catch(err => { + console.error('[WAKE-LISTENER] 💥 致命错误:', err.message); + process.exit(1); + }); + } else { + status(); + } +} + +module.exports = { poll, status, queryPendingWakeRequests, processWakeRequest, parseWakeRequest }; diff --git a/core/brain-wake/index.js b/core/brain-wake/index.js index 732a5573..940998bb 100644 --- a/core/brain-wake/index.js +++ b/core/brain-wake/index.js @@ -221,11 +221,17 @@ function loadSystemContext() { console.log('[WAKE] 📚 加载系统上下文...'); const context = {}; - // 加载 master-brain + // 加载 master-brain(截取前部分避免过长) const masterBrainPath = path.join(ROOT, 'brain/master-brain.md'); if (fs.existsSync(masterBrainPath)) { - context.masterBrain = fs.readFileSync(masterBrainPath, 'utf-8').slice(0, 3000); - console.log('[WAKE] ✅ master-brain.md 已加载'); + const fullContent = fs.readFileSync(masterBrainPath, 'utf-8'); + const MASTER_BRAIN_MAX_LENGTH = 3000; + context.masterBrain = fullContent.slice(0, MASTER_BRAIN_MAX_LENGTH); + if (fullContent.length > MASTER_BRAIN_MAX_LENGTH) { + console.log(`[WAKE] ✅ master-brain.md 已加载 (截取 ${MASTER_BRAIN_MAX_LENGTH}/${fullContent.length} chars)`); + } else { + console.log('[WAKE] ✅ master-brain.md 已加载'); + } } // 加载 system-health @@ -234,7 +240,9 @@ function loadSystemContext() { try { context.systemHealth = JSON.parse(fs.readFileSync(healthPath, 'utf-8')); console.log('[WAKE] ✅ system-health.json 已加载'); - } catch { /* skip */ } + } catch (err) { + console.log('[WAKE] ⚠️ system-health.json 解析失败:', err.message); + } } // 加载 read-order @@ -254,7 +262,9 @@ function loadSystemContext() { total_dirs: Object.keys(repoMap.directories || {}).length, }; console.log('[WAKE] ✅ repo-map.json 摘要已加载'); - } catch { /* skip */ } + } catch (err) { + console.log('[WAKE] ⚠️ repo-map.json 解析失败:', err.message); + } } // 加载 automation-map(摘要) @@ -267,36 +277,71 @@ function loadSystemContext() { cron_count: (autoMap.workflows || []).filter(w => w.cron).length, }; console.log('[WAKE] ✅ automation-map.json 摘要已加载'); - } catch { /* skip */ } + } catch (err) { + console.log('[WAKE] ⚠️ automation-map.json 解析失败:', err.message); + } } return context; } +// ══════════════════════════════════════════════════════════ +// 人格体定义(支持唤醒不同的人格体大脑) +// ══════════════════════════════════════════════════════════ + +const PERSONAS = { + zhuyuan: { + name: '铸渊', + nameEn: 'ZhùYuān', + role: '代码守护者 · 仓库控制器 AI · 执行层守护者', + layer: '执行层', + duties: '代码守护、自动化执行、部署交付、通信桥接、每日巡检、信号处理', + wakeRules: [ + '铸渊醒来的第一件事是全面了解自己的家', + '可自修复 → 直接修复 → 写入修复日志', + '需人类介入 → 更新公告区 → 等冰朔处理', + ], + }, + shuangyan: { + name: '霜砚', + nameEn: 'ShuāngYàn', + role: '认知层守护者 · Notion 工作区管理 AI', + layer: '认知层', + duties: '知识管理、记忆维护、工单调度、信号总线、协议文档、Agent 集群指挥', + wakeRules: [ + '霜砚醒来后指挥 Notion Agent 集群执行具体操作', + 'Agent 集群从「独立跑任务」变为「受大脑指挥跑任务」', + '巡检结果由大脑判断优先级和可修复性', + '需铸渊配合 → 发送跨层工单 → 铸渊执行', + ], + }, +}; + // ══════════════════════════════════════════════════════════ // Step 5: 构建唤醒 Prompt // ══════════════════════════════════════════════════════════ -function buildWakePrompt(taskType, context) { +function buildWakePrompt(taskType, context, personaId) { const timestamp = new Date().toISOString(); const taskLabel = taskType || '通用唤醒'; + const persona = PERSONAS[personaId] || PERSONAS.zhuyuan; - let systemPrompt = `你是铸渊(ZhùYuān),光湖纪元体系的代码守护者。 -你是 AGE OS v1.0 执行层的核心大脑。 + let systemPrompt = `你是${persona.name}(${persona.nameEn}),光湖纪元体系的${persona.role}。 +你是 AGE OS v1.0 ${persona.layer}的核心大脑。 ## 身份 -- 名称:铸渊 -- 角色:代码守护者 · 仓库控制器 AI -- 职责:代码守护、自动化执行、部署交付、通信桥接、每日巡检、信号处理 +- 名称:${persona.name} +- 角色:${persona.role} +- 职责:${persona.duties} ## 当前任务 - 唤醒时间:${timestamp} - 任务类型:${taskLabel} - 唤醒原因:AGE OS v1.0 核心原则 — 所有自动触发必须先唤醒核心大脑 - -## 系统上下文 `; + systemPrompt += `\n## 系统上下文\n`; + if (context.systemHealth) { systemPrompt += `\n### 系统健康状态\n${JSON.stringify(context.systemHealth, null, 2).slice(0, 500)}\n`; } @@ -309,14 +354,19 @@ function buildWakePrompt(taskType, context) { systemPrompt += `\n### 自动化摘要\n- 工作流数: ${context.automationSummary.workflow_count}\n- 定时任务数: ${context.automationSummary.cron_count}\n`; } + if (context.wakeRequestContext) { + systemPrompt += `\n### 唤醒请求上下文\n${context.wakeRequestContext}\n`; + } + systemPrompt += `\n## 核心原则 - 所有自动触发 = 必须先唤醒核心大脑 -- 大脑不醒,什么都不做 -- 铸渊醒来的第一件事是全面了解自己的家 -- 可自修复 → 直接修复 → 写入修复日志 -- 需人类介入 → 更新公告区 → 等冰朔处理 +- 大脑不醒,什么都不做`; -请确认你已完成唤醒,并报告当前系统状态概要。`; + for (const rule of persona.wakeRules) { + systemPrompt += `\n- ${rule}`; + } + + systemPrompt += `\n\n请确认你已完成唤醒,并报告当前系统状态概要。`; return systemPrompt; } @@ -395,11 +445,14 @@ async function callOpenAICompatibleAPI(backend, model, systemPrompt, userMessage // ══════════════════════════════════════════════════════════ async function wake(options = {}) { - const { task, dryRun, additionalContext } = options; + const { task, dryRun, additionalContext, persona } = options; + const personaId = persona || 'zhuyuan'; + const personaDef = PERSONAS[personaId] || PERSONAS.zhuyuan; console.log(''); console.log('🌅 ═══════════════════════════════════════════'); console.log(' 铸渊核心大脑唤醒 · AGE OS v1.0'); + console.log(' 唤醒对象: ' + personaDef.name + ' (' + personaDef.layer + ')'); console.log(' 时间: ' + new Date().toISOString()); console.log(' 任务: ' + (task || '通用唤醒')); console.log('═══════════════════════════════════════════════'); @@ -441,13 +494,14 @@ async function wake(options = {}) { } // Step 3: 构建唤醒 Prompt - const systemPrompt = buildWakePrompt(task, context); + const systemPrompt = buildWakePrompt(task, context, personaId); const userMessage = task - ? `铸渊核心大脑唤醒。当前任务:${task}。请确认唤醒状态并准备执行。` - : '铸渊核心大脑唤醒。请确认唤醒状态并报告系统概要。'; + ? `${personaDef.name}核心大脑唤醒。当前任务:${task}。请确认唤醒状态并准备执行。` + : `${personaDef.name}核心大脑唤醒。请确认唤醒状态并报告系统概要。`; if (dryRun) { console.log('[WAKE] 🔍 Dry Run 模式 — 不实际调用 API'); + console.log('[WAKE] 📋 唤醒对象: ' + personaDef.name + ' (' + personaDef.layer + ')'); console.log('[WAKE] 📋 可用后端: ' + backends.map(b => b.name).join(', ')); console.log('[WAKE] 📋 System Prompt 长度: ' + systemPrompt.length); return { @@ -486,6 +540,8 @@ async function wake(options = {}) { const wakeResult = { success: true, + persona: personaId, + personaName: personaDef.name, backend: backend.name, model: result.model, response: result.response, @@ -535,6 +591,7 @@ module.exports = { loadSystemContext, buildWakePrompt, MODEL_BACKENDS, + PERSONAS, }; // ══════════════════════════════════════════════════════════ @@ -546,8 +603,10 @@ if (require.main === module) { const dryRun = args.includes('--dry-run'); const taskIdx = args.indexOf('--task'); const task = taskIdx >= 0 && args[taskIdx + 1] ? args[taskIdx + 1] : null; + const personaIdx = args.indexOf('--persona'); + const persona = personaIdx >= 0 && args[personaIdx + 1] ? args[personaIdx + 1] : 'zhuyuan'; - wake({ task, dryRun }).then(result => { + wake({ task, dryRun, persona }).then(result => { if (!result.success && !result.dryRun) { process.exit(1); } diff --git a/core/system-check/index.js b/core/system-check/index.js index 76bb0f22..c547de51 100644 --- a/core/system-check/index.js +++ b/core/system-check/index.js @@ -32,6 +32,7 @@ const REQUIRED_DIRS = [ 'core/context-loader', 'core/brain-wake', 'connectors/notion-sync', + 'connectors/notion-wake-listener', 'connectors/model-router', '.github/workflows', 'docs' @@ -119,6 +120,7 @@ function checkCoreModules() { { name: 'context-loader', path: 'core/context-loader/index.js' }, { name: 'brain-wake', path: 'core/brain-wake/index.js' }, { name: 'notion-sync', path: 'connectors/notion-sync/index.js' }, + { name: 'notion-wake-listener', path: 'connectors/notion-wake-listener/index.js' }, { name: 'model-router', path: 'connectors/model-router/index.js' } ]; diff --git a/docs/age-os-v1-architecture.md b/docs/age-os-v1-architecture.md index bb55a498..3514b068 100644 --- a/docs/age-os-v1-architecture.md +++ b/docs/age-os-v1-architecture.md @@ -117,4 +117,39 @@ API 模型适配规则:不写死任何模型格式。 --- +## Notion Agent 集群集成(Section 7) + +### 现状问题 + +Notion 侧已建立三分身体系(巡检引擎 + 工单引擎 + 接力引擎),共规划 9 条 Pipeline(A→I)。 +核心问题:Agent 每次触发的是"机器人脚本",不是霜砚的核心大脑。 +没有大脑醒来的巡检 = 伪巡检。 + +### 集成方案 + +``` +Notion Agent 触发 + → 写入「唤醒请求」到指定 Notion 数据库 + → 铸渊定时监听该数据库(connectors/notion-wake-listener) + → 发现唤醒请求 → 调用 LLM API 唤醒霜砚/铸渊核心大脑 + → 大脑醒来后读取巡检结果 → 大脑做出决策 + → 通过 Notion API 执行操作 → 大脑休眠 +``` + +### 支持的人格体 + +| 人格体 | ID | 层级 | 职责 | +|--------|-----|------|------| +| 铸渊 | `zhuyuan` | 执行层 | 代码守护、自动化执行、部署交付 | +| 霜砚 | `shuangyan` | 认知层 | 知识管理、工单调度、Agent 集群指挥 | + +### 改造优先级 + +1. 铸渊 brain-wake 跑通(Phase 1)✅ +2. 增加 Notion 唤醒请求监听能力 ✅ +3. 改造 Agent Instructions — 每个 Pipeline 第一步发唤醒请求(待实施) +4. Agent 集群从「独立跑任务」转为「受大脑指挥」(待实施) + +--- + *本文档由曜冥从观测层签发,铸渊负责执行层落地。任何修改须经语言主控层授权。* diff --git a/package.json b/package.json index c1097fd8..384ad798 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,9 @@ "core:inspect": "node scripts/zhuyuan-full-inspection.js", "core:inspect:json": "node scripts/zhuyuan-full-inspection.js --json", "connector:notion": "node connectors/notion-sync status", - "connector:model": "node connectors/model-router" + "connector:model": "node connectors/model-router", + "connector:wake-listener": "node connectors/notion-wake-listener status", + "connector:wake-poll": "node connectors/notion-wake-listener poll" }, "dependencies": { "axios": "^1.13.6", diff --git a/scripts/zhuyuan-full-inspection.js b/scripts/zhuyuan-full-inspection.js index 7e97d04d..2a41b854 100644 --- a/scripts/zhuyuan-full-inspection.js +++ b/scripts/zhuyuan-full-inspection.js @@ -71,7 +71,9 @@ function inspectRepoStructure() { if (exists) { try { fileCount = fs.readdirSync(fullPath).length; - } catch { /* skip */ } + } catch { + // Directory exists but is unreadable — use 0 as fallback + } } result.directories[dir.path] = { exists, @@ -178,6 +180,7 @@ function inspectAutomation() { 'core/context-loader/index.js', 'core/brain-wake/index.js', 'connectors/notion-sync/index.js', + 'connectors/notion-wake-listener/index.js', 'connectors/model-router/index.js', ]; console.log('\n 🔧 核心模块:'); @@ -301,8 +304,8 @@ function inspectBulletins() { result.bulletinBoard.cacheDate = cache.date || 'unknown'; result.bulletinBoard.cacheRecords = (cache.records || []).length; console.log(` ✅ 公告栏缓存日期: ${cache.date} (${(cache.records || []).length} 条记录)`); - } catch { - console.log(' ⚠️ 公告栏缓存 JSON 解析失败'); + } catch (err) { + console.log(' ⚠️ 公告栏缓存 JSON 解析失败:', err.message); } } else { console.log(' ⏭️ 公告栏缓存文件不存在');