Merge pull request #108 from qinfendebingshuo/copilot/fix-automated-inspection-process
feat: AGE OS v1.0 Phase 1 — core brain wake, multi-model router, full inspection, Notion Agent cluster integration
This commit is contained in:
commit
ac20b9da74
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 "✅ 监听流程结束"
|
||||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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` | 执行层结构文档 |
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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' }
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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 集群从「独立跑任务」转为「受大脑指挥」(待实施)
|
||||
|
||||
---
|
||||
|
||||
*本文档由曜冥从观测层签发,铸渊负责执行层落地。任何修改须经语言主控层授权。*
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 <file>: 保存到文件
|
||||
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 };
|
||||
Loading…
Reference in New Issue