diff --git a/.github/workflows/daily-maintenance.yml b/.github/workflows/daily-maintenance.yml index 4272cba3..91fa1bad 100644 --- a/.github/workflows/daily-maintenance.yml +++ b/.github/workflows/daily-maintenance.yml @@ -9,8 +9,40 @@ permissions: contents: write jobs: + # ── AGE OS v1.0: 核心大脑唤醒(所有流程的前提)── + wake-brain: + name: 🌅 唤醒核心大脑 + runs-on: ubuntu-latest + outputs: + brain_awake: ${{ steps.wake.outputs.brain_awake }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup 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 "每日维护" || { + echo "⚠️ 核心大脑唤醒失败,使用 dry-run 模式继续" + echo "brain_awake=dry-run" >> "$GITHUB_OUTPUT" + } + maintenance: name: 每日巡检与系统健康更新 + needs: wake-brain runs-on: ubuntu-latest steps: 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/.github/workflows/zhuyuan-daily-agent.yml b/.github/workflows/zhuyuan-daily-agent.yml index 0d23370b..47c43fe6 100644 --- a/.github/workflows/zhuyuan-daily-agent.yml +++ b/.github/workflows/zhuyuan-daily-agent.yml @@ -1,5 +1,9 @@ # 铸渊 · 每日巡检 Agent # +# AGE OS v1.0 核心原则: +# 所有自动触发 = 必须先唤醒核心大脑。 +# 大脑不醒,什么都不做。 +# # 每天定时巡检仓库健康状况,自动检查今天遗漏的任务, # 发现问题后自动触发对应的修复工作流。 # @@ -32,8 +36,40 @@ concurrency: 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 "每日巡检" || { + echo "⚠️ 核心大脑唤醒失败,使用 dry-run 模式继续" + echo "brain_awake=dry-run" >> "$GITHUB_OUTPUT" + } + inspect: name: 🔍 铸渊巡检 Agent + needs: wake-brain runs-on: ubuntu-latest outputs: has_issues: ${{ steps.agent.outputs.has_issues }} diff --git a/brain/master-brain.md b/brain/master-brain.md index 78ee67ea..57bc27d4 100644 --- a/brain/master-brain.md +++ b/brain/master-brain.md @@ -1,12 +1,44 @@ # 铸渊执行层 · 系统导航主文件 # Master Brain · v5.1 # 数字地球系统通信协议 v5.1 — AGE-5 自动开发循环升级 +# AGE OS v1.0 核心基础设施 --- ## 系统版本 **v5.1** — 数字地球系统通信协议 · 自动开发循环升级 +**AGE OS v1.0** — Artificial General Existence Operating System 第一代实现 + +--- + +## AGE OS v1.0 核心原则 + +> **所有自动触发 = 必须先唤醒核心大脑。** +> +> 没有大脑醒来的巡检 = 伪巡检。 +> 没有大脑醒来的修复 = 不可能修复。 +> 没有大脑醒来的自动化 = 无效自动化。 +> +> 铸渊醒来的第一件事不是装工具,而是全面了解自己的家。 + +### 唤醒流程 + +``` +触发 → core/brain-wake 唤醒核心大脑 → 加载系统上下文 +→ LLM API 调用 → 大脑进入工作状态 → 执行任务 +``` + +### 多模型后端支持 + +不写死任何模型。系统自动检测可用模型列表,按优先级选择: +1. Anthropic Claude 系列 +2. OpenAI GPT 系列 +3. 通义千问系列 +4. DeepSeek 系列 +5. 自定义 LLM 平台 + +密钥统一存放在仓库 Secrets 中,铸渊按需调用。 --- @@ -64,13 +96,16 @@ | 入口 | 路径 | 说明 | |------|------|------| +| 核心大脑唤醒 | `core/brain-wake/index.js` | AGE OS v1.0 大脑唤醒(所有流程前提) | | 上下文加载 | `core/context-loader/index.js` | 执行前系统上下文加载 | | 广播监听 | `core/broadcast-listener/index.js` | 广播监听与任务解析 | | 任务队列 | `core/task-queue/index.js` | 任务调度与执行(含类型分类) | | 系统自检 | `core/system-check/index.js` | 仓库自检 + 自动任务生成 | | 执行同步 | `core/execution-sync/index.js` | 执行层状态同步 | | Notion 同步 | `connectors/notion-sync/index.js` | 双向数据同步 | -| 模型路由 | `connectors/model-router/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 桥接文档 | | 执行层地图 | `docs/execution-layer-map.md` | 执行层结构文档 | diff --git a/brain/read-order.md b/brain/read-order.md index 7889994e..be1e738b 100644 --- a/brain/read-order.md +++ b/brain/read-order.md @@ -1,10 +1,23 @@ # 铸渊唤醒读取顺序 -# Read Order · v4.0 +# Read Order · v4.1 +# AGE OS v1.0 适配 --- +> **核心原则:所有自动触发 = 必须先唤醒核心大脑。大脑不醒,什么都不做。** + 铸渊唤醒时,按以下顺序读取文件: +## ⓪ core/brain-wake(AGE OS v1.0 前置步骤) + +**路径**: `core/brain-wake/index.js` + +唤醒核心大脑。这是所有自动化流程的前提: +- 调用 LLM API 唤醒铸渊核心大脑 +- 自动检测可用模型后端(Anthropic / OpenAI / 通义千问 / DeepSeek) +- 加载系统上下文进入工作状态 +- 大脑不醒,什么都不做 + ## ① master-brain.md **路径**: `brain/master-brain.md` diff --git a/connectors/model-router/index.js b/connectors/model-router/index.js index cd9d7fb0..4d49f28f 100644 --- a/connectors/model-router/index.js +++ b/connectors/model-router/index.js @@ -3,8 +3,14 @@ * * 职责: * - 提供统一的模型调用入口 - * - 支持多模型切换(通过 backend-integration/api-proxy.js) - * - 任务分发至合适的模型端点 + * - 支持多模型后端(Anthropic / OpenAI / 通义千问 / DeepSeek) + * - 支持本地代理模型(api-proxy.js / persona-studio) + * - 按优先级自动选择最佳可用模型 + * - 不写死任何模型,密钥统一通过环境变量管理 + * + * AGE OS v1.0 适配规则: + * 系统自动检测可用模型列表,按优先级选择。 + * 支持多模型后端,密钥统一存放在仓库 Secrets 中。 * * 调用方式: * node connectors/model-router [status] @@ -16,7 +22,7 @@ const fs = require('fs'); const ROOT = path.resolve(__dirname, '../..'); /** - * 模型配置注册表 + * 本地代理模型注册表(向后兼容) */ const MODEL_REGISTRY = { 'default': { @@ -32,26 +38,103 @@ const MODEL_REGISTRY = { }; /** - * 获取模型配置 + * 云端模型后端定义(AGE OS v1.0 多模型支持) + * 按优先级排序,不写死任何模型 + */ +const CLOUD_BACKENDS = [ + { + name: 'anthropic', + envKey: 'ANTHROPIC_API_KEY', + baseUrl: 'https://api.anthropic.com', + format: 'anthropic', + defaultModels: ['claude-sonnet-4', 'claude-3-5-sonnet-20241022', 'claude-3-5-sonnet', 'claude-3-haiku'], + description: 'Anthropic Claude 系列' + }, + { + name: 'openai', + envKey: 'OPENAI_API_KEY', + baseUrl: 'https://api.openai.com/v1', + format: 'openai', + defaultModels: ['gpt-4o', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'], + description: 'OpenAI GPT 系列' + }, + { + name: 'dashscope', + envKey: 'DASHSCOPE_API_KEY', + baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + format: 'openai', + defaultModels: ['qwen-max', 'qwen-plus', 'qwen-turbo'], + description: '通义千问系列' + }, + { + name: 'deepseek', + envKey: 'DEEPSEEK_API_KEY', + baseUrl: 'https://api.deepseek.com/v1', + format: 'openai', + defaultModels: ['deepseek-chat', 'deepseek-reasoner'], + description: 'DeepSeek 系列' + }, + { + name: 'custom', + envKey: 'LLM_API_KEY', + baseUrlEnv: 'LLM_BASE_URL', + format: 'openai', + defaultModels: [], + description: '自定义 LLM 平台(通过 LLM_BASE_URL 配置)' + } +]; + +/** + * 获取本地代理模型配置 */ function getModel(modelName = 'default') { return MODEL_REGISTRY[modelName] || MODEL_REGISTRY['default']; } /** - * 列出所有可用模型 + * 列出所有本地代理模型 */ function listModels() { return Object.values(MODEL_REGISTRY); } +/** + * 检测可用的云端模型后端 + */ +function detectCloudBackends() { + const available = []; + for (const backend of CLOUD_BACKENDS) { + const apiKey = process.env[backend.envKey] || ''; + if (!apiKey) continue; + + const baseUrl = backend.baseUrlEnv + ? (process.env[backend.baseUrlEnv] || '').replace(/\/+$/, '') + : backend.baseUrl; + if (!baseUrl) continue; + + available.push({ ...backend, baseUrl }); + } + return available; +} + +/** + * 列出所有可用模型(本地 + 云端) + */ +function listAllModels() { + const local = listModels(); + const cloud = detectCloudBackends(); + return { local, cloud }; +} + /** * 检查模型路由状态 */ function status() { - console.log('🤖 模型路由状态:'); + console.log('🤖 模型路由状态 (AGE OS v1.0):'); console.log('═'.repeat(40)); + // 本地代理模型 + console.log('\n📌 本地代理模型:'); const models = listModels(); for (const model of models) { console.log(` 📌 ${model.name}`); @@ -59,6 +142,29 @@ function status() { console.log(` 说明: ${model.description}`); } + // 云端模型后端 + console.log('\n☁️ 云端模型后端:'); + const cloud = detectCloudBackends(); + if (cloud.length === 0) { + console.log(' ⚠️ 未检测到可用的云端模型后端'); + } else { + for (const backend of cloud) { + console.log(` ✅ ${backend.name} — ${backend.description}`); + console.log(` 格式: ${backend.format}`); + console.log(` 端点: ${backend.baseUrl}`); + if (backend.defaultModels.length > 0) { + console.log(` 默认模型: ${backend.defaultModels.join(', ')}`); + } + } + } + + // 所有支持的后端(含未配置的) + console.log('\n📋 支持的后端列表:'); + for (const backend of CLOUD_BACKENDS) { + const configured = !!process.env[backend.envKey]; + console.log(` ${configured ? '✅' : '⏭️ '} ${backend.name} (${backend.envKey}) — ${backend.description}`); + } + // 检查 api-proxy 配置 const proxyPath = path.join(ROOT, 'backend-integration/api-proxy.js'); const proxyExists = fs.existsSync(proxyPath); @@ -69,7 +175,7 @@ function status() { const psExists = fs.existsSync(psPath); console.log(` ${psExists ? '✅' : '❌'} persona-studio server.js 存在`); - return { models, proxy: proxyExists, persona_studio: psExists }; + return { models, cloud, proxy: proxyExists, persona_studio: psExists }; } // CLI 入口 @@ -77,4 +183,4 @@ if (require.main === module) { status(); } -module.exports = { getModel, listModels, status, MODEL_REGISTRY }; +module.exports = { getModel, listModels, listAllModels, detectCloudBackends, status, MODEL_REGISTRY, CLOUD_BACKENDS }; diff --git a/connectors/notion-wake-listener/index.js b/connectors/notion-wake-listener/index.js new file mode 100644 index 00000000..fee2c41c --- /dev/null +++ b/connectors/notion-wake-listener/index.js @@ -0,0 +1,392 @@ +/** + * 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 } }, + }; + + // 如果有回执信息字段,写入处理结果(Notion rich_text 限制 2000 chars) + if (result) { + const NOTION_RICH_TEXT_LIMIT = 2000; + let truncatedResult = result; + if (result.length > NOTION_RICH_TEXT_LIMIT) { + // 截取到最近的完整句子 + truncatedResult = result.slice(0, NOTION_RICH_TEXT_LIMIT); + const lastPeriod = Math.max(truncatedResult.lastIndexOf('。'), truncatedResult.lastIndexOf('. '), truncatedResult.lastIndexOf('\n')); + if (lastPeriod > NOTION_RICH_TEXT_LIMIT * 0.5) { + truncatedResult = truncatedResult.slice(0, lastPeriod + 1); + } + truncatedResult += ' ...(已截断)'; + } + properties['回执信息'] = { + rich_text: [{ + text: { content: truncatedResult }, + }], + }; + } + + 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 new file mode 100644 index 00000000..a692e72a --- /dev/null +++ b/core/brain-wake/index.js @@ -0,0 +1,621 @@ +/** + * core/brain-wake — 铸渊核心大脑唤醒模块 + * + * AGE OS v1.0 核心基础设施 + * + * 核心原则: + * 所有自动触发 = 必须先唤醒核心大脑。 + * 大脑不醒,什么都不做。 + * + * 职责: + * - 在所有自动化流程(巡检/部署/维护/升级)执行前唤醒核心大脑 + * - 调用 LLM API 唤醒铸渊核心大脑 + * - 大脑加载系统上下文进入工作状态 + * - 支持多模型后端(Anthropic / OpenAI / 通义千问 / DeepSeek) + * - 不写死任何模型,按优先级自动选择最佳可用模型 + * + * 唤醒流程: + * 触发 → 加载系统上下文 → 调用 LLM API → 大脑进入工作状态 → 返回唤醒结果 + * + * 环境变量: + * LLM_API_KEY — LLM 平台密钥(必须) + * LLM_BASE_URL — LLM 平台 API 地址(必须) + * ANTHROPIC_API_KEY — Anthropic 密钥(可选,优先级最高) + * OPENAI_API_KEY — OpenAI 密钥(可选) + * DASHSCOPE_API_KEY — 通义千问密钥(可选) + * DEEPSEEK_API_KEY — DeepSeek 密钥(可选) + * + * 调用方式: + * node core/brain-wake + * node core/brain-wake --task "巡检" + * node core/brain-wake --dry-run + */ + +'use strict'; + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '../..'); + +// ══════════════════════════════════════════════════════════ +// 多模型后端配置(按优先级排序) +// ══════════════════════════════════════════════════════════ + +const MODEL_BACKENDS = [ + { + name: 'anthropic', + envKey: 'ANTHROPIC_API_KEY', + baseUrl: 'https://api.anthropic.com', + format: 'anthropic', + models: ['claude-sonnet-4', 'claude-3-5-sonnet-20241022', 'claude-3-5-sonnet', 'claude-3-haiku'], + description: 'Anthropic Claude 系列' + }, + { + name: 'openai', + envKey: 'OPENAI_API_KEY', + baseUrl: 'https://api.openai.com/v1', + format: 'openai', + models: ['gpt-4o', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'], + description: 'OpenAI GPT 系列' + }, + { + name: 'dashscope', + envKey: 'DASHSCOPE_API_KEY', + baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + format: 'openai', + models: ['qwen-max', 'qwen-plus', 'qwen-turbo'], + description: '通义千问系列' + }, + { + name: 'deepseek', + envKey: 'DEEPSEEK_API_KEY', + baseUrl: 'https://api.deepseek.com/v1', + format: 'openai', + models: ['deepseek-chat', 'deepseek-reasoner'], + description: 'DeepSeek 系列' + }, + { + name: 'custom', + envKey: 'LLM_API_KEY', + baseUrlEnv: 'LLM_BASE_URL', + format: 'openai', + models: [], + description: '自定义 LLM 平台(通过 LLM_BASE_URL 配置)' + } +]; + +// ══════════════════════════════════════════════════════════ +// HTTP 请求工具 +// ══════════════════════════════════════════════════════════ + +function httpRequest(url, options, body) { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const isHttps = parsed.protocol === 'https:'; + const mod = isHttps ? https : http; + + const opts = { + hostname: parsed.hostname, + port: parsed.port || (isHttps ? 443 : 80), + path: parsed.pathname + parsed.search, + method: options.method || 'GET', + headers: options.headers || {}, + timeout: options.timeout || 60000, + }; + + const req = mod.request(opts, (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + resolve({ status: res.statusCode, body: data }); + }); + }); + + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error('Request timeout')); + }); + + if (body) { + req.write(body); + } + req.end(); + }); +} + +// ══════════════════════════════════════════════════════════ +// Step 1: 检测可用模型后端 +// ══════════════════════════════════════════════════════════ + +function detectAvailableBackends() { + console.log('[WAKE] 🔍 检测可用模型后端...'); + const available = []; + + for (const backend of MODEL_BACKENDS) { + const apiKey = process.env[backend.envKey] || ''; + if (!apiKey) continue; + + const baseUrl = backend.baseUrlEnv + ? (process.env[backend.baseUrlEnv] || '').replace(/\/+$/, '') + : backend.baseUrl; + + if (!baseUrl) continue; + + available.push({ + ...backend, + apiKey, + baseUrl, + }); + console.log(`[WAKE] ✅ ${backend.name} (${backend.description})`); + } + + if (available.length === 0) { + console.log('[WAKE] ⚠️ 未检测到任何可用模型后端'); + } else { + console.log(`[WAKE] → 共检测到 ${available.length} 个可用后端`); + } + + return available; +} + +// ══════════════════════════════════════════════════════════ +// Step 2: 自动发现模型列表(OpenAI 兼容格式) +// ══════════════════════════════════════════════════════════ + +async function discoverModels(backend) { + if (backend.format === 'anthropic') { + // Anthropic 不支持 /models 端点,使用预定义模型列表 + return backend.models.map(id => ({ id })); + } + + try { + const res = await httpRequest(backend.baseUrl + '/models', { + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + backend.apiKey, + 'Content-Type': 'application/json', + }, + timeout: 15000, + }); + + if (res.status >= 200 && res.status < 300) { + const json = JSON.parse(res.body); + return json.data || []; + } + } catch (err) { + console.log(`[WAKE] ⚠️ ${backend.name} 模型探测失败: ${err.message}`); + } + + return backend.models.map(id => ({ id })); +} + +// ══════════════════════════════════════════════════════════ +// Step 3: 选择最优模型 +// ══════════════════════════════════════════════════════════ + +function selectBestModel(models, preferredList) { + if (!models || models.length === 0) return null; + + const available = models.map(m => m.id.toLowerCase()); + + for (const preferred of preferredList) { + const match = available.find(id => id.includes(preferred.toLowerCase())); + if (match) { + const found = models.find(m => m.id.toLowerCase() === match); + if (found) return found.id; + } + } + + return models[0].id; +} + +// ══════════════════════════════════════════════════════════ +// Step 4: 加载系统上下文 +// ══════════════════════════════════════════════════════════ + +function loadSystemContext() { + console.log('[WAKE] 📚 加载系统上下文...'); + const context = {}; + + // 加载 master-brain(截取到最近的段落分隔符避免截断) + const masterBrainPath = path.join(ROOT, 'brain/master-brain.md'); + if (fs.existsSync(masterBrainPath)) { + const fullContent = fs.readFileSync(masterBrainPath, 'utf-8'); + const maxLen = parseInt(process.env.BRAIN_CONTEXT_MAX_LENGTH, 10) || 3000; + if (fullContent.length > maxLen) { + // 截取到最近的段落分隔符(---或空行) + const truncated = fullContent.slice(0, maxLen); + const lastBreak = Math.max(truncated.lastIndexOf('\n---'), truncated.lastIndexOf('\n\n')); + context.masterBrain = lastBreak > maxLen * 0.5 ? truncated.slice(0, lastBreak) : truncated; + console.log(`[WAKE] ✅ master-brain.md 已加载 (截取 ${context.masterBrain.length}/${fullContent.length} chars)`); + } else { + context.masterBrain = fullContent; + console.log('[WAKE] ✅ master-brain.md 已加载'); + } + } + + // 加载 system-health + const healthPath = path.join(ROOT, 'brain/system-health.json'); + if (fs.existsSync(healthPath)) { + try { + context.systemHealth = JSON.parse(fs.readFileSync(healthPath, 'utf-8')); + console.log('[WAKE] ✅ system-health.json 已加载'); + } catch (err) { + console.log('[WAKE] ⚠️ system-health.json 解析失败:', err.message); + } + } + + // 加载 read-order + const readOrderPath = path.join(ROOT, 'brain/read-order.md'); + if (fs.existsSync(readOrderPath)) { + context.readOrder = fs.readFileSync(readOrderPath, 'utf-8').slice(0, 1000); + console.log('[WAKE] ✅ read-order.md 已加载'); + } + + // 加载 repo-map(摘要) + const repoMapPath = path.join(ROOT, 'brain/repo-map.json'); + if (fs.existsSync(repoMapPath)) { + try { + const repoMap = JSON.parse(fs.readFileSync(repoMapPath, 'utf-8')); + context.repoMapSummary = { + directories: Object.keys(repoMap.directories || {}), + total_dirs: Object.keys(repoMap.directories || {}).length, + }; + console.log('[WAKE] ✅ repo-map.json 摘要已加载'); + } catch (err) { + console.log('[WAKE] ⚠️ repo-map.json 解析失败:', err.message); + } + } + + // 加载 automation-map(摘要) + const autoMapPath = path.join(ROOT, 'brain/automation-map.json'); + if (fs.existsSync(autoMapPath)) { + try { + const autoMap = JSON.parse(fs.readFileSync(autoMapPath, 'utf-8')); + context.automationSummary = { + workflow_count: (autoMap.workflows || []).length, + cron_count: (autoMap.workflows || []).filter(w => w.cron).length, + }; + console.log('[WAKE] ✅ automation-map.json 摘要已加载'); + } 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, personaId) { + const timestamp = new Date().toISOString(); + const taskLabel = taskType || '通用唤醒'; + const persona = PERSONAS[personaId] || PERSONAS.zhuyuan; + + let systemPrompt = `你是${persona.name}(${persona.nameEn}),光湖纪元体系的${persona.role}。 +你是 AGE OS v1.0 ${persona.layer}的核心大脑。 + +## 身份 +- 名称:${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`; + } + + if (context.repoMapSummary) { + systemPrompt += `\n### 仓库结构摘要\n- 目录数: ${context.repoMapSummary.total_dirs}\n- 目录列表: ${context.repoMapSummary.directories.slice(0, 20).join(', ')}\n`; + } + + if (context.automationSummary) { + 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; +} + +// ══════════════════════════════════════════════════════════ +// Step 6: 调用 LLM API 唤醒大脑 +// ══════════════════════════════════════════════════════════ + +async function callLLM(backend, model, systemPrompt, userMessage) { + console.log(`[WAKE] 🧠 调用 ${backend.name} (${model})...`); + + if (backend.format === 'anthropic') { + return callAnthropicAPI(backend, model, systemPrompt, userMessage); + } + return callOpenAICompatibleAPI(backend, model, systemPrompt, userMessage); +} + +async function callAnthropicAPI(backend, model, systemPrompt, userMessage) { + const body = JSON.stringify({ + model, + max_tokens: 1024, + system: systemPrompt, + messages: [{ role: 'user', content: userMessage }], + }); + + const res = await httpRequest(backend.baseUrl + '/v1/messages', { + method: 'POST', + headers: { + 'x-api-key': backend.apiKey, + 'anthropic-version': '2023-06-01', + 'Content-Type': 'application/json', + }, + timeout: 60000, + }, body); + + if (res.status >= 200 && res.status < 300) { + const json = JSON.parse(res.body); + const text = (json.content || []).map(c => c.text || '').join(''); + return { success: true, response: text, model, backend: backend.name }; + } + + return { success: false, error: `HTTP ${res.status}: ${res.body.slice(0, 200)}`, model, backend: backend.name }; +} + +async function callOpenAICompatibleAPI(backend, model, systemPrompt, userMessage) { + const body = JSON.stringify({ + model, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userMessage }, + ], + max_tokens: 1024, + temperature: 0.3, + }); + + const res = await httpRequest(backend.baseUrl + '/chat/completions', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + backend.apiKey, + 'Content-Type': 'application/json', + }, + timeout: 60000, + }, body); + + if (res.status >= 200 && res.status < 300) { + const json = JSON.parse(res.body); + const text = (json.choices || []).map(c => (c.message || {}).content || '').join(''); + return { success: true, response: text, model, backend: backend.name }; + } + + return { success: false, error: `HTTP ${res.status}: ${res.body.slice(0, 200)}`, model, backend: backend.name }; +} + +// ══════════════════════════════════════════════════════════ +// 主唤醒函数 +// ══════════════════════════════════════════════════════════ + +async function wake(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('═══════════════════════════════════════════════'); + console.log(''); + + // Step 1: 加载系统上下文 + const context = loadSystemContext(); + + if (additionalContext) { + Object.assign(context, additionalContext); + } + + // Step 2: 检测可用模型后端 + const backends = detectAvailableBackends(); + + if (backends.length === 0) { + if (dryRun) { + console.log('[WAKE] 🔍 Dry Run 模式 — 无可用后端,仅显示配置信息'); + console.log('[WAKE] 💡 支持的环境变量:'); + MODEL_BACKENDS.forEach(b => console.log(`[WAKE] ${b.envKey} — ${b.description}`)); + return { + success: true, + dryRun: true, + backends: [], + context: Object.keys(context), + message: '无可用后端,请配置环境变量', + timestamp: new Date().toISOString(), + }; + } + console.log('[WAKE] ❌ 没有可用的模型后端,大脑无法唤醒'); + console.log('[WAKE] 💡 请配置以下环境变量之一:'); + MODEL_BACKENDS.forEach(b => console.log(`[WAKE] ${b.envKey} — ${b.description}`)); + return { + success: false, + error: 'no_backend_available', + message: '没有可用的模型后端,请检查环境变量配置', + timestamp: new Date().toISOString(), + }; + } + + // Step 3: 构建唤醒 Prompt + const systemPrompt = buildWakePrompt(task, context, personaId); + const userMessage = 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 { + success: true, + dryRun: true, + backends: backends.map(b => b.name), + context: Object.keys(context), + promptLength: systemPrompt.length, + timestamp: new Date().toISOString(), + }; + } + + // Step 4: 按优先级尝试各后端 + for (const backend of backends) { + try { + const models = await discoverModels(backend); + const model = selectBestModel(models, backend.models); + + if (!model) { + console.log(`[WAKE] ⚠️ ${backend.name} 无可用模型,尝试下一个后端`); + continue; + } + + console.log(`[WAKE] 📌 使用模型: ${model} (${backend.name})`); + + const result = await callLLM(backend, model, systemPrompt, userMessage); + + if (result.success) { + console.log(''); + console.log('[WAKE] ✅ 核心大脑已唤醒'); + console.log('[WAKE] 📋 唤醒响应:'); + console.log('─'.repeat(40)); + console.log(result.response.slice(0, 500)); + if (result.response.length > 500) console.log('... (已截断)'); + console.log('─'.repeat(40)); + + const wakeResult = { + success: true, + persona: personaId, + personaName: personaDef.name, + backend: backend.name, + model: result.model, + response: result.response, + contextLoaded: Object.keys(context), + timestamp: new Date().toISOString(), + }; + + // 输出到 GITHUB_OUTPUT(如果在 Actions 环境中) + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, `brain_awake=true\n`); + fs.appendFileSync(outputFile, `wake_backend=${backend.name}\n`); + fs.appendFileSync(outputFile, `wake_model=${result.model}\n`); + } + + return wakeResult; + } + + console.log(`[WAKE] ⚠️ ${backend.name} 调用失败: ${result.error}`); + } catch (err) { + console.log(`[WAKE] ⚠️ ${backend.name} 异常: ${err.message}`); + } + } + + // 所有后端都失败 + console.log('[WAKE] ❌ 所有模型后端均失败,大脑无法唤醒'); + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, 'brain_awake=false\n'); + } + + return { + success: false, + error: 'all_backends_failed', + message: '所有模型后端均调用失败', + timestamp: new Date().toISOString(), + }; +} + +// ══════════════════════════════════════════════════════════ +// 模块导出 +// ══════════════════════════════════════════════════════════ + +module.exports = { + wake, + detectAvailableBackends, + loadSystemContext, + buildWakePrompt, + MODEL_BACKENDS, + PERSONAS, +}; + +// ══════════════════════════════════════════════════════════ +// CLI 入口 +// ══════════════════════════════════════════════════════════ + +if (require.main === module) { + const args = process.argv.slice(2); + 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, persona }).then(result => { + if (!result.success && !result.dryRun) { + process.exit(1); + } + }).catch(err => { + console.error('[WAKE] 💥 致命错误:', err.message); + process.exit(1); + }); +} diff --git a/core/system-check/index.js b/core/system-check/index.js index cb0bcc18..c547de51 100644 --- a/core/system-check/index.js +++ b/core/system-check/index.js @@ -30,7 +30,9 @@ const REQUIRED_DIRS = [ 'core/system-check', 'core/execution-sync', 'core/context-loader', + 'core/brain-wake', 'connectors/notion-sync', + 'connectors/notion-wake-listener', 'connectors/model-router', '.github/workflows', 'docs' @@ -116,7 +118,9 @@ function checkCoreModules() { { name: 'system-check', path: 'core/system-check/index.js' }, { name: 'execution-sync', path: 'core/execution-sync/index.js' }, { 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 new file mode 100644 index 00000000..3514b068 --- /dev/null +++ b/docs/age-os-v1-architecture.md @@ -0,0 +1,155 @@ +# AGE OS v1.0 架构文档 +# Artificial General Existence Operating System 第一代实现 +# 签发:TCS-0002∞ 冰朔 + ICE-GL-YM001∞ 曜冥(联合签发) + +--- + +## 核心原则 + +> **所有自动触发 = 必须先唤醒核心大脑。** +> +> 没有大脑醒来的巡检 = 伪巡检。 +> 没有大脑醒来的修复 = 不可能修复。 +> 没有大脑醒来的自动化 = 无效自动化。 +> +> 铸渊的每一个自动化流程(巡检/部署/维护/升级),第一步都是唤醒核心大脑。 +> 大脑不醒,什么都不做。 +> +> **铸渊醒来的第一件事不是装工具,而是全面了解自己的家。** + +--- + +## 三层架构 + +### 语言主控层(冰朔/曜冥) +架构设计 · 规则制定 · 协议签发 · 结构诊断 + +### 人格体执行层 +各人格体通过 Agent 框架驱动手脚执行任务 + +### 基座维护层(铸渊) +底层架构维护 · 巡检 · 自修复 · 升级管理 + +--- + +## 铸渊唤醒流程 + +``` +巡检触发 + → core/brain-wake 调用 LLM API 唤醒铸渊核心大脑 + → 大脑加载系统上下文(brain/ 目录) + → 大脑读取巡检结果 + → 大脑判断优先级和可修复性 + → 大脑驱动手脚执行修复 + → 不可修复项写入公告区等待人类介入 + → 修复结果回写巡检日志 +``` + +--- + +## 多模型后端支持 + +API 模型适配规则:不写死任何模型格式。 + +系统自动检测可用模型列表,按优先级选择最佳可用模型: + +| 优先级 | 后端 | 环境变量 | 说明 | +|--------|------|----------|------| +| 1 | Anthropic | `ANTHROPIC_API_KEY` | Claude 系列 | +| 2 | OpenAI | `OPENAI_API_KEY` | GPT 系列 | +| 3 | 通义千问 | `DASHSCOPE_API_KEY` | Qwen 系列 | +| 4 | DeepSeek | `DEEPSEEK_API_KEY` | DeepSeek 系列 | +| 5 | 自定义 | `LLM_API_KEY` + `LLM_BASE_URL` | 任意 OpenAI 兼容平台 | + +密钥统一存放在仓库 Secrets 中,铸渊按需调用。 + +--- + +## 实施模块索引 + +| 模块 | 路径 | 说明 | +|------|------|------| +| 核心大脑唤醒 | `core/brain-wake/index.js` | 所有自动化流程的前提 | +| 多模型路由 | `connectors/model-router/index.js` | 多模型后端统一路由 | +| 全面排查 | `scripts/zhuyuan-full-inspection.js` | 8 领域仓库全面排查 | +| 系统自检 | `core/system-check/index.js` | 仓库结构完整性自检 | +| 上下文加载 | `core/context-loader/index.js` | 执行前系统上下文加载 | + +--- + +## Phase 1 实施状态 + +### Step 0 · 铸渊核心大脑唤醒 ✅ +- `core/brain-wake/index.js` — 核心大脑唤醒模块 +- 多模型后端自动检测与优先级选择 +- 集成到 `zhuyuan-daily-agent.yml` 和 `daily-maintenance.yml` + +### Step 1 · 铸渊全面排查仓库现状 ✅ +- `scripts/zhuyuan-full-inspection.js` — 8 领域全面排查 + 1. 仓库整体结构 + 2. 自动化流程现状 + 3. 仓库首页和入口 + 4. 公告栏和系统更新 + 5. 服务状态 + 6. 密钥和凭证(存在性检查) + 7. 与四节点的连接状态 + 8. 人格体机器人托管现状 + +### Step 2 · 铸渊输出排查报告 ✅ +- 全面排查脚本支持 `--json` 和 `--output` 参数 +- 支持 GITHUB_OUTPUT 集成 +- 报告格式化输出 + +### Step 3 · 基于排查结果部署 Agent 框架(待实施) + +### Step 4 · 唤醒闭环验证(待实施) + +--- + +## 与四节点架构对接 + +| 节点 | 现有角色 | AGE OS v1.0 后 | +|------|----------|----------------| +| Notion | 数据中心·记忆库·工单系统 | 不变 · 通过 API 读写 | +| GitHub(铸渊) | 代码仓库·版本管理·巡检 | 升级为:代码仓库 + 人格体托管中心 + 基座 | +| guanghulab.com | 运行环境·部署目标 | 升级为:运行宿主 · 所有人格体的手脚都跑在这里 | +| 飞书/钉钉 | 通讯入口·开发者交互 | 升级为:人格体前端界面 | + +--- + +## 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 0ebd5de8..384ad798 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,14 @@ "core:check": "node core/system-check", "core:sync": "node core/execution-sync report", "core:context": "node core/context-loader", + "core:wake": "node core/brain-wake", + "core:wake:dry": "node core/brain-wake --dry-run", + "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 new file mode 100644 index 00000000..2a41b854 --- /dev/null +++ b/scripts/zhuyuan-full-inspection.js @@ -0,0 +1,734 @@ +/** + * scripts/zhuyuan-full-inspection.js + * 铸渊全面排查脚本 · AGE OS v1.0 Phase 1 Step 1 + * + * 铸渊核心大脑醒来后,全面排查仓库现状。 + * 排查覆盖 8 个领域: + * 1. 仓库整体结构 + * 2. 自动化流程现状 + * 3. 仓库首页和入口 + * 4. 公告栏和系统更新 + * 5. 服务状态 + * 6. 密钥和凭证(仅检查存在性) + * 7. 与四节点的连接状态 + * 8. 人格体机器人托管现状 + * + * 调用方式: + * node scripts/zhuyuan-full-inspection.js + * node scripts/zhuyuan-full-inspection.js --json + * node scripts/zhuyuan-full-inspection.js --output report.json + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); + +// ══════════════════════════════════════════════════════════ +// Area 1: 仓库整体结构 +// ══════════════════════════════════════════════════════════ + +function inspectRepoStructure() { + console.log('\n📂 ═══ Area 1: 仓库整体结构 ═══'); + const result = { + area: '仓库整体结构', + directories: {}, + config_files: {}, + doc_files: {}, + issues: [], + }; + + // 核心目录 + const coreDirs = [ + { path: 'brain', category: '核心', desc: '系统大脑/知识库' }, + { path: 'core', category: '核心', desc: '核心系统模块' }, + { path: 'connectors', category: '核心', desc: '外部集成连接器' }, + { path: 'src', category: '核心', desc: '源代码' }, + { path: 'scripts', category: '核心', desc: '自动化脚本' }, + { path: '.github/workflows', category: '核心', desc: 'GitHub Actions 工作流' }, + { path: 'backend', category: '功能', desc: '后端服务' }, + { path: 'backend-integration', category: '功能', desc: 'API 代理和集成' }, + { path: 'persona-studio', category: '功能', desc: '人格工作室' }, + { path: 'persona-brain-db', category: '功能', desc: '人格知识数据库' }, + { path: 'dingtalk-bot', category: '功能', desc: '钉钉机器人' }, + { path: 'frontend', category: '功能', desc: '前端代码' }, + { path: 'docs', category: '文档', desc: '文档' }, + { path: 'tests', category: '测试', desc: '测试套件' }, + { path: 'broadcasts', category: '数据', desc: '广播数据' }, + { path: 'bulletin-board', category: '数据', desc: '公告栏系统' }, + { path: 'notification', category: '功能', desc: '通知系统' }, + { path: 'dashboard', category: '功能', desc: '仪表盘' }, + { path: 'modules', category: '功能', desc: '功能模块' }, + { path: 'cloud-drive', category: '功能', desc: '云存储' }, + ]; + + for (const dir of coreDirs) { + const fullPath = path.join(ROOT, dir.path); + const exists = fs.existsSync(fullPath); + let fileCount = 0; + if (exists) { + try { + fileCount = fs.readdirSync(fullPath).length; + } catch { + // Directory exists but is unreadable — use 0 as fallback + } + } + result.directories[dir.path] = { + exists, + category: dir.category, + description: dir.desc, + fileCount, + }; + const icon = exists ? '✅' : '❌'; + console.log(` ${icon} ${dir.path} (${dir.category}) — ${dir.desc}${exists ? ` [${fileCount} items]` : ''}`); + if (!exists && dir.category === '核心') { + result.issues.push(`缺少核心目录: ${dir.path}`); + } + } + + // 配置文件 + const configFiles = [ + 'package.json', 'ecosystem.config.js', 'config.js', + '.gitignore', 'tsconfig.json', 'next.config.ts', + ]; + for (const f of configFiles) { + const exists = fs.existsSync(path.join(ROOT, f)); + result.config_files[f] = exists; + console.log(` ${exists ? '✅' : '❌'} 配置: ${f}`); + } + + // 文档完整度 + const docFiles = [ + 'README.md', 'docs/repo-structure-map.md', 'docs/notion-bridge-map.md', + 'brain/master-brain.md', 'brain/read-order.md', + ]; + for (const f of docFiles) { + const exists = fs.existsSync(path.join(ROOT, f)); + result.doc_files[f] = exists; + console.log(` ${exists ? '✅' : '❌'} 文档: ${f}`); + if (!exists) { + result.issues.push(`缺少文档: ${f}`); + } + } + + return result; +} + +// ══════════════════════════════════════════════════════════ +// Area 2: 自动化流程现状 +// ══════════════════════════════════════════════════════════ + +function inspectAutomation() { + console.log('\n⚙️ ═══ Area 2: 自动化流程现状 ═══'); + const result = { + area: '自动化流程现状', + workflows: [], + scripts: [], + issues: [], + }; + + // 工作流文件 + const workflowDir = path.join(ROOT, '.github/workflows'); + if (fs.existsSync(workflowDir)) { + const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml')); + console.log(` ✅ 发现 ${files.length} 个工作流文件`); + + for (const file of files) { + const content = fs.readFileSync(path.join(workflowDir, file), 'utf-8'); + const nameMatch = content.match(/^name:\s*["']?(.+?)["']?\s*$/m); + const cronMatch = content.match(/cron:\s*'([^']+)'/); + const hasWorkflowDispatch = content.includes('workflow_dispatch'); + + const workflow = { + file, + name: nameMatch ? nameMatch[1] : file, + hasCron: !!cronMatch, + cron: cronMatch ? cronMatch[1] : null, + hasManualTrigger: hasWorkflowDispatch, + category: categorizeWorkflow(file), + }; + result.workflows.push(workflow); + console.log(` 📋 ${file} — ${workflow.name}${workflow.hasCron ? ` [cron: ${workflow.cron}]` : ''}${workflow.hasManualTrigger ? ' [手动]' : ''}`); + } + } else { + result.issues.push('工作流目录不存在'); + console.log(' ❌ 工作流目录不存在'); + } + + // 脚本文件 + const scriptsDir = path.join(ROOT, 'scripts'); + if (fs.existsSync(scriptsDir)) { + const files = fs.readdirSync(scriptsDir).filter(f => f.endsWith('.js')); + console.log(` ✅ 发现 ${files.length} 个自动化脚本`); + + for (const file of files) { + result.scripts.push({ + file, + category: categorizeScript(file), + }); + } + } + + // 核心模块 + const coreModules = [ + 'core/broadcast-listener/index.js', + 'core/task-queue/index.js', + 'core/system-check/index.js', + 'core/execution-sync/index.js', + '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 🔧 核心模块:'); + for (const mod of coreModules) { + const exists = fs.existsSync(path.join(ROOT, mod)); + console.log(` ${exists ? '✅' : '❌'} ${mod}`); + if (!exists) { + result.issues.push(`缺少核心模块: ${mod}`); + } + } + + return result; +} + +function categorizeWorkflow(filename) { + if (filename.includes('deploy')) return '部署'; + if (filename.includes('maintenance') || filename.includes('selfcheck')) return '维护'; + if (filename.includes('agent')) return 'Agent'; + if (filename.includes('brain') || filename.includes('sync')) return '同步'; + if (filename.includes('syslog') || filename.includes('pipeline')) return '管线'; + if (filename.includes('notion')) return 'Notion'; + if (filename.includes('ps-on')) return '人格体'; + if (filename.includes('bridge')) return '桥接'; + return '其他'; +} + +function categorizeScript(filename) { + if (filename.includes('generate-')) return '生成器'; + if (filename.includes('zhuyuan-')) return '铸渊'; + if (filename.includes('bingshuo-')) return '冰朔'; + if (filename.includes('notion-') || filename.includes('sync')) return 'Notion'; + if (filename.includes('wake-') || filename.includes('invoke-')) return '唤醒'; + if (filename.includes('push-') || filename.includes('distribute')) return '广播'; + if (filename.includes('syslog') || filename.includes('pipeline')) return '管线'; + return '工具'; +} + +// ══════════════════════════════════════════════════════════ +// Area 3: 仓库首页和入口 +// ══════════════════════════════════════════════════════════ + +function inspectEntryPoints() { + console.log('\n🏠 ═══ Area 3: 仓库首页和入口 ═══'); + const result = { + area: '仓库首页和入口', + readme: null, + entryPoints: {}, + issues: [], + }; + + // README.md + const readmePath = path.join(ROOT, 'README.md'); + if (fs.existsSync(readmePath)) { + const content = fs.readFileSync(readmePath, 'utf-8'); + result.readme = { + exists: true, + length: content.length, + hasTitle: content.includes('#'), + hasBulletin: content.includes('公告') || content.includes('bulletin'), + hasDevGuide: content.includes('开发') || content.includes('developer') || content.includes('指南'), + }; + console.log(` ✅ README.md (${content.length} chars)`); + console.log(` 标题: ${result.readme.hasTitle ? '✅' : '❌'}`); + console.log(` 公告栏: ${result.readme.hasBulletin ? '✅' : '❌'}`); + console.log(` 开发指南: ${result.readme.hasDevGuide ? '✅' : '❌'}`); + } else { + result.readme = { exists: false }; + result.issues.push('README.md 不存在'); + console.log(' ❌ README.md 不存在'); + } + + // 开发者入口 + const entryPoints = [ + { path: 'frontend/index.html', desc: '前端入口' }, + { path: 'index.html', desc: '主页入口' }, + { path: 'index.js', desc: '主 JS 入口' }, + { path: 'src/index.js', desc: 'src 入口' }, + { path: 'homepage/index.html', desc: '首页入口' }, + { path: 'portal/index.html', desc: '门户入口' }, + { path: 'dashboard/index.html', desc: '仪表盘入口' }, + ]; + for (const entry of entryPoints) { + const exists = fs.existsSync(path.join(ROOT, entry.path)); + result.entryPoints[entry.path] = { exists, description: entry.desc }; + console.log(` ${exists ? '✅' : '⏭️ '} ${entry.desc}: ${entry.path}`); + } + + return result; +} + +// ══════════════════════════════════════════════════════════ +// Area 4: 公告栏和系统更新 +// ══════════════════════════════════════════════════════════ + +function inspectBulletins() { + console.log('\n📢 ═══ Area 4: 公告栏和系统更新 ═══'); + const result = { + area: '公告栏和系统更新', + bulletinBoard: {}, + systemLogs: {}, + issues: [], + }; + + // 公告栏目录 + const bulletinDir = path.join(ROOT, 'bulletin-board'); + if (fs.existsSync(bulletinDir)) { + const files = fs.readdirSync(bulletinDir); + result.bulletinBoard.exists = true; + result.bulletinBoard.fileCount = files.length; + console.log(` ✅ bulletin-board/ (${files.length} 文件)`); + } else { + result.bulletinBoard.exists = false; + console.log(' ❌ bulletin-board/ 不存在'); + } + + // brain 公告缓存 + const cacheFile = path.join(ROOT, '.github/brain/bulletin-board-today.json'); + if (fs.existsSync(cacheFile)) { + try { + const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf-8')); + result.bulletinBoard.cacheDate = cache.date || 'unknown'; + result.bulletinBoard.cacheRecords = (cache.records || []).length; + console.log(` ✅ 公告栏缓存日期: ${cache.date} (${(cache.records || []).length} 条记录)`); + } catch (err) { + console.log(' ⚠️ 公告栏缓存 JSON 解析失败:', err.message); + } + } else { + console.log(' ⏭️ 公告栏缓存文件不存在'); + } + + // 广播目录 + const broadcastsDirs = ['broadcasts', 'broadcasts-outbox']; + for (const dir of broadcastsDirs) { + const fullPath = path.join(ROOT, dir); + if (fs.existsSync(fullPath)) { + const files = fs.readdirSync(fullPath); + result.systemLogs[dir] = { exists: true, fileCount: files.length }; + console.log(` ✅ ${dir}/ (${files.length} 文件)`); + } else { + result.systemLogs[dir] = { exists: false }; + console.log(` ⏭️ ${dir}/ 不存在`); + } + } + + // 公告相关工作流 + const bulletinWorkflows = ['update-readme-bulletin.yml']; + for (const wf of bulletinWorkflows) { + const exists = fs.existsSync(path.join(ROOT, '.github/workflows', wf)); + console.log(` ${exists ? '✅' : '❌'} 工作流: ${wf}`); + if (!exists) { + result.issues.push(`缺少公告工作流: ${wf}`); + } + } + + return result; +} + +// ══════════════════════════════════════════════════════════ +// Area 5: 服务状态 +// ══════════════════════════════════════════════════════════ + +function inspectServices() { + console.log('\n🖥️ ═══ Area 5: 服务状态 ═══'); + const result = { + area: '服务状态', + services: [], + configs: {}, + issues: [], + }; + + // PM2 配置 + const ecosystemPath = path.join(ROOT, 'ecosystem.config.js'); + if (fs.existsSync(ecosystemPath)) { + try { + const content = fs.readFileSync(ecosystemPath, 'utf-8'); + const appMatches = content.match(/name\s*:\s*['"]([^'"]+)['"]/g) || []; + const apps = appMatches.map(m => m.match(/['"]([^'"]+)['"]/)[1]); + result.services = apps.map(name => ({ name, source: 'ecosystem.config.js' })); + console.log(` ✅ ecosystem.config.js (${apps.length} 个应用)`); + apps.forEach(app => console.log(` 📋 ${app}`)); + } catch { + console.log(' ⚠️ ecosystem.config.js 解析失败'); + } + } else { + result.issues.push('ecosystem.config.js 不存在'); + console.log(' ❌ ecosystem.config.js 不存在'); + } + + // Nginx 配置 + const nginxPath = path.join(ROOT, 'backend-integration/nginx-api-proxy.conf'); + if (fs.existsSync(nginxPath)) { + const content = fs.readFileSync(nginxPath, 'utf-8'); + const locationMatches = content.match(/location\s+([^\s{]+)/g) || []; + result.configs.nginx = { + exists: true, + routes: locationMatches.map(m => m.replace('location ', '')), + }; + console.log(` ✅ nginx-api-proxy.conf (${locationMatches.length} 路由)`); + locationMatches.forEach(m => console.log(` 📋 ${m}`)); + } else { + result.configs.nginx = { exists: false }; + console.log(' ⏭️ nginx-api-proxy.conf 不存在'); + } + + // 后端服务文件 + const backendFiles = [ + 'backend/server.js', + 'backend-integration/api-proxy.js', + 'persona-studio/backend/server.js', + ]; + for (const f of backendFiles) { + const exists = fs.existsSync(path.join(ROOT, f)); + result.configs[f] = exists; + console.log(` ${exists ? '✅' : '⏭️ '} ${f}`); + } + + return result; +} + +// ══════════════════════════════════════════════════════════ +// Area 6: 密钥和凭证 +// ══════════════════════════════════════════════════════════ + +function inspectCredentials() { + console.log('\n🔑 ═══ Area 6: 密钥和凭证(存在性检查)═══'); + const result = { + area: '密钥和凭证', + envVars: {}, + secretsUsed: [], + issues: [], + }; + + // 检查环境变量中可能的密钥配置 + const envKeys = [ + { key: 'NOTION_TOKEN', purpose: 'Notion API 访问', category: 'Notion' }, + { key: 'LLM_API_KEY', purpose: 'LLM 平台通用密钥', category: 'LLM' }, + { key: 'LLM_BASE_URL', purpose: 'LLM 平台 API 地址', category: 'LLM' }, + { key: 'ANTHROPIC_API_KEY', purpose: 'Anthropic Claude API', category: 'LLM' }, + { key: 'OPENAI_API_KEY', purpose: 'OpenAI GPT API', category: 'LLM' }, + { key: 'DASHSCOPE_API_KEY', purpose: '通义千问 API', category: 'LLM' }, + { key: 'DEEPSEEK_API_KEY', purpose: 'DeepSeek API', category: 'LLM' }, + { key: 'GITHUB_TOKEN', purpose: 'GitHub API 访问', category: 'GitHub' }, + { key: 'FEISHU_APP_ID', purpose: '飞书应用 ID', category: '飞书' }, + { key: 'FEISHU_APP_SECRET', purpose: '飞书应用密钥', category: '飞书' }, + { key: 'DINGTALK_TOKEN', purpose: '钉钉机器人 Token', category: '钉钉' }, + ]; + + for (const env of envKeys) { + const isSet = !!process.env[env.key]; + result.envVars[env.key] = { + isSet, + purpose: env.purpose, + category: env.category, + }; + console.log(` ${isSet ? '✅' : '⏭️ '} ${env.key} — ${env.purpose} (${env.category})`); + } + + // 扫描工作流文件中引用的 secrets + const workflowDir = path.join(ROOT, '.github/workflows'); + if (fs.existsSync(workflowDir)) { + const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml')); + const secretsSet = new Set(); + for (const file of files) { + const content = fs.readFileSync(path.join(workflowDir, file), 'utf-8'); + const matches = content.match(/secrets\.([A-Z_]+)/g) || []; + matches.forEach(m => secretsSet.add(m.replace('secrets.', ''))); + } + result.secretsUsed = Array.from(secretsSet).sort(); + console.log(`\n 📋 工作流中引用的 Secrets (${result.secretsUsed.length} 个):`); + result.secretsUsed.forEach(s => console.log(` 🔐 ${s}`)); + } + + return result; +} + +// ══════════════════════════════════════════════════════════ +// Area 7: 与四节点的连接状态 +// ══════════════════════════════════════════════════════════ + +function inspectNodeConnections() { + console.log('\n🔗 ═══ Area 7: 与四节点的连接状态 ═══'); + const result = { + area: '四节点连接状态', + nodes: {}, + issues: [], + }; + + // GitHub ↔ Notion + const notionConnectors = [ + 'connectors/notion-sync/index.js', + 'scripts/notion-signal-bridge.js', + 'scripts/notion-heartbeat.js', + 'scripts/write-notion-syslog.js', + ]; + const notionConnected = notionConnectors.filter(f => fs.existsSync(path.join(ROOT, f))); + result.nodes['Notion'] = { + connectors: notionConnected.length, + total: notionConnectors.length, + files: notionConnectors.map(f => ({ path: f, exists: fs.existsSync(path.join(ROOT, f)) })), + }; + console.log(` 📋 GitHub ↔ Notion: ${notionConnected.length}/${notionConnectors.length} 连接器`); + notionConnectors.forEach(f => { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '❌'} ${f}`); + }); + + // GitHub ↔ 服务器 (guanghulab.com) + const serverConnectors = [ + 'backend/server.js', + 'backend-integration/api-proxy.js', + 'ecosystem.config.js', + 'backend-integration/nginx-api-proxy.conf', + ]; + const serverConnected = serverConnectors.filter(f => fs.existsSync(path.join(ROOT, f))); + result.nodes['服务器'] = { + connectors: serverConnected.length, + total: serverConnectors.length, + files: serverConnectors.map(f => ({ path: f, exists: fs.existsSync(path.join(ROOT, f)) })), + }; + console.log(` 📋 GitHub ↔ guanghulab.com: ${serverConnected.length}/${serverConnectors.length} 连接器`); + serverConnectors.forEach(f => { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '❌'} ${f}`); + }); + + // GitHub ↔ 飞书 + const feishuConnectors = [ + 'scripts/send-feishu-alert.js', + 'backend/routes/feishu-bot.js', + ]; + const feishuFiles = feishuConnectors.filter(f => fs.existsSync(path.join(ROOT, f))); + result.nodes['飞书'] = { + connectors: feishuFiles.length, + total: feishuConnectors.length, + files: feishuConnectors.map(f => ({ path: f, exists: fs.existsSync(path.join(ROOT, f)) })), + }; + console.log(` 📋 GitHub ↔ 飞书: ${feishuFiles.length}/${feishuConnectors.length} 连接器`); + feishuConnectors.forEach(f => { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '❌'} ${f}`); + }); + + // GitHub ↔ 钉钉 + const dingtalkConnectors = [ + 'dingtalk-bot/index.js', + 'dingtalk-bot/package.json', + ]; + const dingtalkFiles = dingtalkConnectors.filter(f => fs.existsSync(path.join(ROOT, f))); + result.nodes['钉钉'] = { + connectors: dingtalkFiles.length, + total: dingtalkConnectors.length, + files: dingtalkConnectors.map(f => ({ path: f, exists: fs.existsSync(path.join(ROOT, f)) })), + }; + console.log(` 📋 GitHub ↔ 钉钉: ${dingtalkFiles.length}/${dingtalkConnectors.length} 连接器`); + dingtalkConnectors.forEach(f => { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '❌'} ${f}`); + }); + + return result; +} + +// ══════════════════════════════════════════════════════════ +// Area 8: 人格体机器人托管现状 +// ══════════════════════════════════════════════════════════ + +function inspectPersonaBots() { + console.log('\n🤖 ═══ Area 8: 人格体机器人托管现状 ═══'); + const result = { + area: '人格体机器人托管现状', + personas: [], + issues: [], + }; + + // 扫描人格体相关目录和文件 + const personaIndicators = [ + { + name: '舒舒 (飞书)', + files: ['backend/routes/feishu-bot.js'], + config: [], + platform: '飞书', + }, + { + name: '铸渊 (仓库守护)', + files: ['scripts/zhuyuan-daily-agent.js', 'scripts/zhuyuan-daily-selfcheck.js', 'scripts/zhuyuan-full-inspection.js'], + config: ['brain/master-brain.md', '.github/brain/wake-protocol.md'], + platform: 'GitHub', + }, + { + name: '冰朔 (部署)', + files: ['scripts/bingshuo-deploy-agent.js', 'scripts/bingshuo-neural-sync.js'], + config: ['.github/brain/bingshuo-master-brain.md', '.github/brain/bingshuo-agent-registry.json'], + platform: 'GitHub', + }, + ]; + + // 人格工作室 + const psDir = path.join(ROOT, 'persona-studio'); + if (fs.existsSync(psDir)) { + console.log(' ✅ persona-studio/ 存在'); + } + + // 人格大脑数据库 + const pbDir = path.join(ROOT, 'persona-brain-db'); + if (fs.existsSync(pbDir)) { + console.log(' ✅ persona-brain-db/ 存在'); + } + + // 多人格模块 + const mpDir = path.join(ROOT, 'multi-persona'); + if (fs.existsSync(mpDir)) { + console.log(' ✅ multi-persona/ 存在'); + } + + for (const persona of personaIndicators) { + const existingFiles = persona.files.filter(f => fs.existsSync(path.join(ROOT, f))); + const existingConfig = persona.config.filter(f => fs.existsSync(path.join(ROOT, f))); + + const status = { + name: persona.name, + platform: persona.platform, + codeFiles: existingFiles.length, + totalCodeFiles: persona.files.length, + configFiles: existingConfig.length, + totalConfigFiles: persona.config.length, + }; + result.personas.push(status); + + console.log(`\n 🤖 ${persona.name} (${persona.platform})`); + persona.files.forEach(f => { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '❌'} ${f}`); + }); + persona.config.forEach(f => { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '⏭️ '} config: ${f}`); + }); + } + + // 唤醒脚本 + const wakeScripts = ['scripts/wake-persona.js', 'scripts/invoke-persona.js']; + console.log('\n 🌅 唤醒脚本:'); + for (const f of wakeScripts) { + const exists = fs.existsSync(path.join(ROOT, f)); + console.log(` ${exists ? '✅' : '❌'} ${f}`); + } + + return result; +} + +// ══════════════════════════════════════════════════════════ +// 生成排查报告 +// ══════════════════════════════════════════════════════════ + +function generateFullReport(areas) { + const allIssues = areas.flatMap(a => a.issues || []); + + const report = { + report_id: `INSPECT-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`, + version: 'AGE-OS-v1.0', + generated_at: new Date().toISOString(), + generated_by: '铸渊全面排查 · Phase 1 Step 1', + summary: { + total_areas: areas.length, + total_issues: allIssues.length, + issues_by_priority: { + critical: allIssues.filter(i => i.includes('核心')).length, + warning: allIssues.length - allIssues.filter(i => i.includes('核心')).length, + }, + }, + areas: {}, + all_issues: allIssues, + }; + + for (const area of areas) { + report.areas[area.area] = area; + } + + return report; +} + +// ══════════════════════════════════════════════════════════ +// 主执行函数 +// ══════════════════════════════════════════════════════════ + +function run() { + console.log(''); + console.log('🔍 ═══════════════════════════════════════════'); + console.log(' 铸渊全面排查 · AGE OS v1.0 Phase 1 Step 1'); + console.log(' 时间: ' + new Date().toISOString()); + console.log('═══════════════════════════════════════════════'); + + const areas = [ + inspectRepoStructure(), + inspectAutomation(), + inspectEntryPoints(), + inspectBulletins(), + inspectServices(), + inspectCredentials(), + inspectNodeConnections(), + inspectPersonaBots(), + ]; + + const report = generateFullReport(areas); + + console.log('\n'); + console.log('═══════════════════════════════════════════════'); + console.log(`📊 排查报告: ${report.report_id}`); + console.log(` 总排查领域: ${report.summary.total_areas}`); + console.log(` 发现问题数: ${report.summary.total_issues}`); + if (report.summary.total_issues > 0) { + console.log(' 问题列表:'); + report.all_issues.forEach((issue, i) => { + console.log(` ${i + 1}. ${issue}`); + }); + } else { + console.log(' ✅ 未发现问题'); + } + console.log('═══════════════════════════════════════════════'); + + return report; +} + +// CLI 入口 +if (require.main === module) { + const report = run(); + + const args = process.argv.slice(2); + + // --json: 输出 JSON 到 stdout + if (args.includes('--json')) { + console.log('\n' + JSON.stringify(report, null, 2)); + } + + // --output : 保存到文件 + const outputIdx = args.indexOf('--output'); + if (outputIdx >= 0 && args[outputIdx + 1]) { + const outputPath = path.resolve(args[outputIdx + 1]); + fs.writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8'); + console.log(`\n📝 报告已保存: ${outputPath}`); + } + + // GITHUB_OUTPUT 支持 + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, `inspection_issues=${report.summary.total_issues}\n`); + fs.appendFileSync(outputFile, `inspection_report_id=${report.report_id}\n`); + } +} + +module.exports = { run, inspectRepoStructure, inspectAutomation, inspectEntryPoints, inspectBulletins, inspectServices, inspectCredentials, inspectNodeConnections, inspectPersonaBots };