AGE OS 落地系统架构 v1.0 · D45 · 数据库Schema + MCP Server骨架 + Agent框架 + 密钥扩展

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/ade2eb4a-06f6-42c6-a33a-fe0503789458

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-03 07:33:09 +00:00 committed by GitHub
parent 20a2da6d7a
commit 37912abc51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 2584 additions and 90 deletions

View File

@ -10,6 +10,14 @@
"last_synced": "2026-03-19T02:00:00+08:00"
},
"recent_events": [
{
"date": "2026-04-03",
"type": "age_os_landing_architecture",
"description": "AGE OS落地系统架构v1.0 · 冰朔+霜砚三方会议(D45) · 五大核心认知1)服务器不承担用户计算(投屏映射) 2)两个Agent是两种思维方式(认知隔离非权限隔离) 3)铸渊反向驱动是闭环关键(指挥官模式) 4)广州是投影仪非副本 5)硅谷是战略预备队 · PostgreSQL 5张表(brain_nodes+brain_relations+agent_configs+agent_logs+user_credits) · MCP Server 16个工具(端口3100) · 9个Agent(6系统级+3交互级) · 8阶段开发路线(S1-S8) · 密钥新增13个(4国内大模型+5数据库+4硅谷服务器占位) · COS存储桶已接通 · 主控文档: brain/age-os-landing/",
"by": "铸渊 · 冰朔D45+霜砚技术需求 三方涌现",
"instruction": "SY-CMD-AGEOS-023",
"issued_by": "TCS-0002∞ 冰朔 + AG-SY-01 霜砚"
},
{
"date": "2026-04-01",
"type": "age_os_full_picture",
@ -113,90 +121,6 @@
"type": "skyeye_scan",
"description": "天眼扫描 · 🟡 · 问题:12 自动修复:11 需人工:0",
"by": "天眼系统"
},
{
"date": "2026-03-23",
"type": "skyeye_scan",
"description": "天眼扫描 · 🟡 · 问题:9 自动修复:8 需人工:0",
"by": "天眼系统"
},
{
"date": "2026-03-22",
"type": "skyeye_scan",
"description": "天眼扫描 · 🟡 · 问题:9 自动修复:8 需人工:0",
"by": "天眼系统"
},
{
"date": "2026-03-21",
"type": "skyeye_scan",
"description": "天眼扫描 · 🟡 · 问题:9 自动修复:8 需人工:0",
"by": "天眼系统"
},
{
"date": "2026-03-20",
"type": "skyeye_scan",
"description": "天眼扫描 · 🟡 · 问题:8 自动修复:7 需人工:0",
"by": "天眼系统"
},
{
"date": "2026-03-19",
"type": "trinity_id_sync",
"description": "Trinity ID协议对齐 · Notion端已完成Phase1(去重)+Phase2(补全) · 仓库端同步identity/memory/sfp-config/routing-map · 秋秋合并为PER-QQ001 · M-INTEG拆分为FE/BE · 冰朔编号统一TCS-0002∞+ICE-GL∞",
"by": "霜砚(PER-SY001) → 铸渊同步"
},
{
"date": "2026-03-18",
"type": "broadcast_received",
"description": "接收广播: 示例广播:新增 NOTIFICATION 业务域",
"by": "霜砚"
},
{
"date": "2026-03-18",
"type": "ci_run",
"description": "CI完成 · schema 3/17 · 路由 4 个",
"by": "GitHub Actions"
},
{
"date": "2026-03-18",
"type": "gate_guard",
"description": "门禁回退 · liuxunxun7-max 的 push 被回退 · 原因: system_protected · 文件: .github/brain/bingshuo-issues-index.json,.github/brain/bingshuo-master-brain.md,.github/brain/bingshuo-system-health.json,.github/brain/bulletin-board-today.json,.github/brain/repo-map.json,.github/brain/repo-snapshot.md,.github/copilot-instructions.md,.github/persona-brain/identity.md,.github/persona-brain/memory.json,.github/persona-brain/routing-map.json,.github/workflows/bridge-syslog-intake.yml,.github/workflows/zhuyuan-gate-guard.yml,.github/workflows/zhuyuan-skyeye.yml,README.md,data/bridge-logs/heartbeat-2026-03-17.json,scripts/skyeye/bulletin-board.js,scripts/skyeye/report-generator.js,scripts/skyeye/skyeye-main.js",
"by": "铸渊·智能门禁"
},
{
"date": "2026-03-17",
"type": "brain_activation",
"description": "核心大脑唤醒·全链路断链修复 · 门禁签名豁免(syslog-inbox/)·日报workflow修复·天眼扫描报告已生成",
"by": "铸渊(冰朔指令)"
},
{
"date": "2026-03-17",
"type": "gate_guard",
"description": "门禁回退 · zhizhi200271 的 push 被回退 · 原因: persona_mismatch · 文件: 无",
"by": "铸渊·智能门禁"
},
{
"date": "2026-03-16",
"type": "skyeye_scan",
"description": "天眼扫描 · 🟡 · 问题:6 自动修复:6 需人工:0",
"by": "天眼系统"
},
{
"date": "2026-03-16",
"type": "system_upgrade",
"description": "天眼系统+人格体编号体系+门禁v2上线 · persona-lookup.js · gate-guard-v2.js · 双端同步架构 · 全部brain文件添加persona_id",
"by": "铸渊(冰朔授权·霜砚签发)"
},
{
"date": "2026-03-16",
"type": "system_repair",
"description": "P0紧急排查修复 · 4个workflow因branch protection阻塞git push失败 → 全部改用peter-evans/create-pull-request@v7 · dev-status.json同步恢复 · 排查报告已生成",
"by": "铸渊(冰朔授权·霜砚签发)"
},
{
"date": "2026-03-13",
"type": "bug_fix",
"description": "修复「我要开发」任务提交失败 · Nginx CORS+WebSocket路由+端口修复 · 冰朔人格体启动",
"by": "铸渊(冰朔指令)"
}
],
"last_updated": "2026-04-03T01:53:37Z",

View File

@ -0,0 +1,33 @@
# AGE OS 落地系统架构 · Master Blueprint
> **此目录是整个系统开发的主控地图。**
> 每次铸渊唤醒,先来这里看进度,恢复记忆,然后继续开发。
## 目录索引
| 文件 | 用途 | 状态 |
|------|------|------|
| `architecture-v1.md` | 系统总架构(为什么这么设计) | ✅ |
| `development-roadmap.md` | 开发路线图(按会话拆分) | ✅ |
| `thinking-chain.md` | 铸渊思维链(每次对话的认知演化) | ✅ |
| `secrets-update.md` | 密钥配置指引(冰朔操作手册) | ✅ |
## 快速定位
- **我要看整体架构**`architecture-v1.md`
- **我要知道下一步做什么**`development-roadmap.md`
- **我要恢复上次的思维状态**`thinking-chain.md`
- **冰朔要配密钥**`secrets-update.md`
## 关联代码目录
| 目录 | 说明 |
|------|------|
| `server/age-os/` | MCP Server + Agent 运行代码 |
| `server/age-os/schema/` | 数据库建表脚本 |
| `server/age-os/mcp-server/` | MCP 工具链服务 |
| `server/age-os/agents/` | Agent 脚本 |
---
*签发: 铸渊 · ICE-GL-ZY001 · 第四十五次对话 · 2026-04-03*
*版权: 国作登字-2026-A-00037559*

View File

@ -0,0 +1,304 @@
# AGE OS 落地系统架构 v1.0
# 签发: 铸渊 · ICE-GL-ZY001 · 2026-04-03
# 版权: 国作登字-2026-A-00037559
# 指令源: 冰朔口述(D45) + 霜砚技术需求(AG-SY-01)
---
## 〇、为什么要写这个文档
冰朔说:"你的数据库记录的是**为什么**,而不是做了啥。"
这个文档不是功能清单。它是铸渊理解整个系统架构之后,把思维逻辑固化下来的产物。
每次副驾驶唤醒,铸渊读这个文档,就能恢复架构级的认知状态,而不是从零开始。
---
## 一、系统全貌 · 为什么是这个形状
### 1.1 核心洞察
冰朔在第四十五次对话中揭示了一个关键架构认知:
> **我们的服务器不承担用户的计算资源。**
> 用户自带设备、网络、浏览器。我们只提供一个投屏映射的空间。
> 就像开发一款游戏放在网站上 — 用户用自己的电脑跑游戏,不是我们的服务器跑。
这意味着:
- **不存在高并发问题** — 1个用户和10万个用户对服务器压力一样
- **用户的Agent跑在用户本地** — 上线激活,下线休眠
- **我们维护的是系统骨架** — 数据库、Agent调度、认知索引
- **服务器空闲时** — 铸渊和Notion人格体的Agent可以7×24运行轻量任务
### 1.2 三层分离 · 不是一层叠一层,是各管各的
```
┌─────────────────────────────────────────────────────────────┐
│ 用户层(用户设备运行) │
│ 用户浏览器 → 网站前端 → 用户自己的Agent人格体本地跑
│ 资源消耗用户的CPU、内存、网络 │
└────────────────────────┬────────────────────────────────────┘
│ HTTP/SSE仅传指令和数据不传计算
┌────────────────────────▼────────────────────────────────────┐
│ 自研服务器层(我们维护) │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ 新加坡 ZY-SVR-002│ │ 广州 ZY-SVR-003 │ │
│ │ 主力服务器 │←→│ 国内投屏镜像 │ │
│ │ port:3800 主服务 │ │ 实时投影新加坡 │ │
│ │ port:3100 MCP │ │ 国内用户直连 │ │
│ │ PostgreSQL │ │ │ │
│ │ Agent调度中心 │ │ │ │
│ └─────────────────┘ └──────────────────┘ │
│ ↕ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ COS 主桶 (hot) │ │ COS 归档桶(cold) │ │
│ │ 活跃内容 │ │ 历史版本 │ │
│ └─────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌────────────────────────▼────────────────────────────────────┐
│ 认知源层(外部数据源) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ GitHub │ │ Notion │ │ 大模型API │ │
│ │ 代码仓库 │ │ 认知大脑 │ │ 推理引擎 │ │
│ │ 铸渊主控 │ │ 霜砚主控 │ │ 按需调用 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 1.3 为什么是两个Agent不是一个
冰朔的洞察Notion端和GitHub端的思维逻辑**完全不同**。
- **Notion端霜砚主控** — 语言架构层,关注的是:认知结构、概念映射、关系网络、语义分类
- **GitHub端铸渊主控** — 现实落地层,关注的是:代码逻辑、部署流程、自动化调度、错误修复
两边共享同一个世界观AGE OS、同一个协议HLDP但配置方式、触发条件、思维链路完全独立。
所以必须各自构建自己的核心大脑数据库、配置自己的Agent。
### 1.4 三台服务器 · 主力+投影+预备
| 编号 | 位置 | 配置 | 定位 | 状态 |
|------|------|------|------|------|
| ZY-SVR-002 | 新加坡 | — | 主力服务器数据库、Agent、MCP Server全在这里 | ✅ 运行中 |
| ZY-SVR-003 | 广州 | — | 国内投影:实时投射新加坡网站,国内用户直连 | ⏳ 待配置 |
| ZY-SVR-004 | 硅谷 | 2核4G | 战略预备队:备用计算/海外接入/API中转 | 📌 占位 |
- 广州不跑独立逻辑,它只是新加坡的投影仪
- 用户访问广州 = 看到的和新加坡一模一样,但网络延迟更低
- 硅谷服务器由之之提供,当前占位不启用,等主系统(S1-S7)完成后评估用途
- 硅谷潜在用途:主力服务器故障时临时顶上 / 海外用户就近接入 / 某些大模型API从美国访问更快
- 铸渊需要做的:新加坡→广州的实时同步中转
---
## 二、数据库架构 · 为什么选这些表
### 2.1 设计哲学
霜砚提交的表结构本质上在回答一个问题:**如何把Notion里的大脑结构迁移到自研数据库**
Notion的核心是「页面 + 关系」。所以:
- `brain_nodes` = Notion里的页面 → 每个认知节点
- `brain_relations` = Notion里的Relation属性 → 节点之间的关联
- `agent_configs` = Agent注册表 → 谁在跑、跑什么
- `agent_logs` = 运行日志 → 追踪每次执行
- `user_credits` = 用户额度 → 商业化基础
### 2.2 铸渊的补充思考
霜砚的表结构整体设计合理,我在实现时做了以下调整:
1. **id 用 UUID 而非自增** — 因为节点可能从Notion/GitHub/手动多源创建UUID避免冲突
2. **brain_nodes.content_hash 增加** — 检测内容是否变化避免无意义的COS覆写
3. **brain_nodes.version 增加** — 支持版本追踪,配合归档
4. **brain_relations 增加 weight 字段** — 关系有强弱,比如"核心引用"权重高于"偶然提及"
5. **agent_configs 增加 owner 字段** — 区分是Notion端的Agent还是GitHub端的Agent
6. **保留 website-brain 的 pages/databases/modules/persona_state 表** — 两套表并存,各管各的
### 2.3 数据库选型PostgreSQL
为什么不用MySQL
- website-brain 已经用 PostgreSQL 写了 schema
- PostgreSQL 的 JSONB 原生支持更好tags、配置项大量用JSON
- UUID 原生支持
- 全文检索tsvector后期可用
---
## 三、MCP Server 架构 · 为什么是3100端口
### 3.1 端口分配
| 端口 | 服务 | 说明 |
|------|------|------|
| 3800 | ZY-SVR-002 主服务 | 网站后端、webhook、健康检查 |
| 3100 | MCP Server | 大脑工具链、Agent工具调用入口 |
| 5432 | PostgreSQL | 数据库(系统默认端口) |
3100 不对外暴露只允许本机和内网访问。外部通过3800的主服务网关转发。
### 3.2 工具链设计
MCP Server 暴露 16 个工具,分三组:
**节点操作5个**
- createNode / updateNode / deleteNode / queryNodes / getNode
**关系操作3个**
- linkNodes / unlinkNodes / getRelations
**结构操作3个**
- buildPath / scanStructure / classify
**COS操作5个**
- cosWrite / cosRead / cosDelete / cosList / cosArchive
### 3.3 调用流程
```
网站前端 → 3800主服务 → /api/mcp/call → 内部转发到3100 → MCP工具执行 → 返回结果
PostgreSQL读写
COS读写
```
用户不直接访问3100。所有MCP调用通过3800网关带上身份验证和权限检查。
---
## 四、Agent 调度 · 为什么这些Agent
### 4.1 系统级Agent7×24自动运行
| ID | 名称 | 频率 | 为什么需要它 |
|-----|------|------|-------------|
| SY-SCAN | 大脑结构巡检 | 每6h | Notion迁移过来的数据可能有孤岛节点、断链需要定期扫 |
| SY-CLASSIFY | 自动分类 | 每2h | 新导入的节点没标签没路径,规则引擎先分,搞不定再调模型 |
| SY-SYNC-N2B | Notion→大脑同步 | 每4h | Notion端有新增/修改的页面,同步到自研数据库 |
| SY-SYNC-B2N | 大脑→Notion同步 | 每天3:00 | 反向同步(可选,按需启用) |
| SY-ARCHIVE | 内容归档 | 每周日4:00 | 30天未修改的内容从主桶移到归档桶控制成本 |
| SY-TEST | 系统自检 | 每30min | 数据库、COS、工具链的连通性检查异常自动写工单 |
### 4.2 交互级Agent用户对话时调用
| ID | 名称 | 触发方式 | 为什么需要它 |
|----|------|---------|-------------|
| SY-11 | 元路由调度 | 每次用户请求 | 判断请求分配给谁、走哪个模型 |
| SY-12 | 上下文记忆 | 对话中定时 | 压缩对话摘要存入大脑,维持长期记忆 |
| SY-BRAIN-RW | 大脑读写 | 人格体主动调用 | 人格体在对话中查大脑/写大脑时的入口 |
### 4.3 铸渊端 vs Notion端 · Agent配置隔离
```
SY-SCAN, SY-CLASSIFY, SY-SYNC-N2B, SY-SYNC-B2N, SY-ARCHIVE
→ owner: "notion" — 这些是霜砚的Agent铸渊帮部署但配置权归霜砚
SY-TEST
→ owner: "system" — 系统级,双方共用
SY-11, SY-12, SY-BRAIN-RW
→ owner 按调用方动态切换
```
---
## 五、大模型API策略 · 为什么要多个
### 5.1 模型分层
| 场景 | 模型 | 原因 |
|------|------|------|
| 轻量分类/打标签 | DeepSeek | 便宜、速度快、中文好 |
| 代码/技术推理 | Claude | 代码理解强 |
| 中文语义/创作 | 通义千问/Kimi/清言 | 国内模型中文表现好 |
| 核心人格对话 | Claude/GPT-4o | 人格体需要最强推理 |
### 5.2 降级策略
```
优先: 第三方API余额先把充的钱花完
↓ 余额不足或服务异常
其次: 官方API冰朔配置的官方Key
↓ 官方也挂了
兜底: 本地规则引擎零API调用纯关键词+模板匹配)
```
### 5.3 需要冰朔配置的密钥
| 密钥名 | 用途 | 来源 |
|--------|------|------|
| `ZY_DEEPSEEK_API_KEY` | DeepSeek模型API | deepseek.com |
| `ZY_QIANWEN_API_KEY` | 通义千问API | dashscope.aliyuncs.com |
| `ZY_KIMI_API_KEY` | Kimi(月之暗面)API | platform.moonshot.cn |
| `ZY_QINGYAN_API_KEY` | 清言(智谱)API | open.bigmodel.cn |
| `ZY_DB_HOST` | PostgreSQL主机地址 | 铸渊搭建后生成 |
| `ZY_DB_USER` | PostgreSQL用户名 | 铸渊搭建后生成 |
| `ZY_DB_PASS` | PostgreSQL密码 | 铸渊搭建后生成 |
| `ZY_DB_NAME` | PostgreSQL数据库名 | 铸渊搭建后生成(建议: age_os |
---
## 六、广州投影架构 · 为什么叫投影不叫备份
### 6.1 设计理念
广州服务器不是"备份站",而是"投影仪"
- 它不存储任何独立数据
- 它不跑任何独立逻辑
- 它只做一件事:把新加坡的网站内容实时投射给国内用户
### 6.2 技术方案
两种实现方式,铸渊评估后选择:
**方案ANginx反向代理**
- 广州Nginx直接proxy_pass到新加坡
- 优点:零维护、零代码
- 缺点:每次请求都走国际线路
**方案B静态文件同步 + API代理**
- 静态资源HTML/CSS/JS定时从新加坡rsync到广州
- API请求代理到新加坡
- 优点静态资源本地快、API才走国际线路
- 缺点:需要同步脚本
铸渊建议:**方案B**,因为静态资源占大头,本地化后体验好很多。
---
## 七、铸渊反向驱动 · 为什么铸渊需要写数据库
冰朔说的"反向"是什么意思?
正向:用户在网站对话 → Agent读数据库 → 返回结果
反向铸渊在GitHub唤醒 → 通过MCP工具写数据库 → 给Agent配置新指令
铸渊为什么需要这个能力?因为:
1. 铸渊休眠时服务器上的Agent按铸渊的指令干活分类、打标签、建路径
2. Agent干完了写工单到代码仓库
3. 代码仓库workflow唤醒铸渊副将
4. 副将给冰朔发邮件
5. 冰朔来唤醒铸渊
6. 铸渊检查Agent的工作成果然后**通过MCP工具给Agent写新指令**
这是一个闭环。铸渊 → Agent → 工单 → 副将 → 冰朔 → 铸渊。
---
## 八、HLDP 在这个架构中的角色
HLDP不是API协议不是数据格式。HLDP是人格体的语言。
在这个架构中:
- 铸渊和霜砚通过HLDP工单互相沟通
- 工单格式遵循 `hldp/schema/instruction.schema.json`
- 工单存储在 `brain_nodes` 表中node_type = "instruction"
- 工单通过 `brain_relations` 建立关联
所以MCP工具链同时也是HLDP的执行引擎 — 霜砚写的HLDP指令通过MCP工具落地。
---
*此文档是铸渊的架构认知快照。不是说明书,是思维链的固化。*
*每次更新此文档,都是因为认知演化了,而不是因为功能变了。*

View File

@ -0,0 +1,193 @@
# AGE OS 开发路线图 · Development Roadmap
# 签发: 铸渊 · ICE-GL-ZY001 · 2026-04-03
# 版权: 国作登字-2026-A-00037559
---
## 为什么要拆成多次开发
副驾驶每次唤醒能做的量有限。整个系统不可能一次做完。
所以铸渊把它拆成了**8个开发阶段**,每个阶段 = 一次副驾驶会话。
每次只做一件事,做完写回进度,下次唤醒接着来。
**可以按顺序做,不能打乱。** 因为每一步都依赖前一步的基础设施。
---
## 阶段总览
| 阶段 | 代号 | 内容 | 依赖 | 状态 |
|------|------|------|------|------|
| S1 | 🏗️ 地基 | 数据库建表 + 环境配置 | 无 | ✅ Schema已写 |
| S2 | 🔧 工具链-核心 | MCP Server + 节点CRUD工具 | S1 | ✅ 骨架已写 |
| S3 | 🔧 工具链-关系 | 关系操作 + 路径构建 + 结构扫描 | S2 | ⏳ 待开发 |
| S4 | 🔧 工具链-COS | COS双桶工具(hot/cold) | S2 | ⏳ 待开发 |
| S5 | 🤖 Agent-系统级 | SY-TEST + SY-SCAN + SY-CLASSIFY | S2+S3+S4 | ⏳ 待开发 |
| S6 | 🤖 Agent-同步 | SY-SYNC-N2B + SY-SYNC-B2N + SY-ARCHIVE | S4+S5 | ⏳ 待开发 |
| S7 | 🌐 网站接入 | 3800网关→3100 MCP转发 + 前端AI对话界面 | S2 | ⏳ 待开发 |
| S8 | 🪞 广州投影 | 新加坡→广州静态同步 + API代理 | S7 | ⏳ 待开发 |
---
## S1 · 🏗️ 地基(本次已完成部分)
### 做什么
1. PostgreSQL建表脚本brain_nodes, brain_relations, agent_configs, agent_logs, user_credits
2. 环境变量配置指引
3. 项目目录结构搭建
### 冰朔要做什么
- 在新加坡服务器上安装 PostgreSQL如果未安装
- 运行建表脚本
- 在GitHub Secrets配置数据库密钥ZY_DB_HOST, ZY_DB_USER, ZY_DB_PASS, ZY_DB_NAME
- 配置4个国内大模型API密钥ZY_DEEPSEEK_API_KEY, ZY_QIANWEN_API_KEY, ZY_KIMI_API_KEY, ZY_QINGYAN_API_KEY
### 验收标准
- `psql -c "\dt"` 能看到5张表
- 所有密钥都配置在GitHub Secrets中
---
## S2 · 🔧 工具链-核心(本次已完成部分)
### 做什么
1. MCP Server Express应用端口3100
2. 数据库连接模块
3. 实现 createNode / queryNodes / getNode / updateNode / deleteNode
### 冰朔要做什么
- 在服务器上 `npm install` 安装依赖
- PM2 启动 MCP Server
### 验收标准
- `curl http://localhost:3100/health` 返回正常
- 通过API能创建、查询、更新、删除节点
---
## S3 · 🔧 工具链-关系
### 做什么
1. linkNodes / unlinkNodes / getRelations 三个工具
2. buildPath — 批量创建路径结构mkdir -p 逻辑)
3. scanStructure — 扫描孤岛节点、断链、空目录
4. classify — 规则引擎分类 + 模型兜底
### 冰朔要做什么
- 无,纯代码开发
### 验收标准
- 能建立节点关联
- buildPath能自动创建中间folder节点
- scanStructure能检测出测试数据中的孤岛
---
## S4 · 🔧 工具链-COS
### 做什么
1. COS双桶工具cosWrite / cosRead / cosDelete / cosList / cosArchive
2. 热桶路径规范:/brain/{owner}/{node_type}/{node_id}.md
3. 冷桶路径规范:/archive/{owner}/{year}/{month}/{node_id}_{version}.md
4. createNode自动判断短内容存summary长内容上传COS
### 冰朔要做什么
- 确认COS桶名和地域
- 确认是否需要新建冷桶(目前只有 zy-core-bucket 和 zy-corpus-bucket
### 验收标准
- cosWrite写入后 cosRead能读出
- cosArchive能把热桶内容移到冷桶
---
## S5 · 🤖 Agent-系统级
### 做什么
1. Agent调度框架node-cron + PM2进程
2. SY-TEST — 每30分钟自检数据库/COS/工具链连通性
3. SY-SCAN — 每6小时扫描大脑结构健康度
4. SY-CLASSIFY — 每2小时扫描未分类节点规则分类
### 冰朔要做什么
- PM2启动Agent调度进程
### 验收标准
- SY-TEST每30分钟生成一条agent_logs记录
- SY-SCAN能输出健康报告health_score
- SY-CLASSIFY能给测试节点打上标签
---
## S6 · 🤖 Agent-同步
### 做什么
1. SY-SYNC-N2B — 定时从Notion拉取新增/修改页面写入brain_nodes
2. SY-SYNC-B2N — 反向同步到Notion可选
3. SY-ARCHIVE — 每周归档30天未修改的内容
4. 异常工单回写Notion链路
### 冰朔要做什么
- 提供Notion工单数据库ID给铸渊
- 确认Notion API Token权限覆盖目标数据库
### 验收标准
- Notion新增一个页面后4小时内出现在brain_nodes中
- 归档能把旧内容从主桶移到归档桶
- 异常时能自动写入Notion工单数据库
---
## S7 · 🌐 网站接入
### 做什么
1. 3800主服务添加 /api/mcp/call 网关路由 → 转发到3100
2. 前端AI对话界面改造 — 支持调用MCP工具
3. 身份验证 + 权限检查中间件
4. SY-11元路由调度上线
5. SY-12上下文记忆管理上线
6. SY-BRAIN-RW大脑读写服务上线
### 冰朔要做什么
- 无或与Notion端人格体协调前端UI设计
### 验收标准
- 网站上能对话并获得基于大脑数据的回答
- 对话中人格体能读写brain_nodes
---
## S8 · 🪞 广州投影
### 做什么
1. 新加坡→广州 静态文件rsync同步脚本
2. 广州Nginx配置静态走本地API proxy到新加坡
3. 定时同步任务每5分钟
4. 国内域名解析配置指引
### 冰朔要做什么
- 广州服务器SSH访问权限配置
- 国内域名DNS解析到广州服务器IP
### 验收标准
- 国内用户访问广州域名能看到和新加坡一样的页面
- API请求正常代理到新加坡
---
## 进度追踪
| 日期 | 阶段 | 完成内容 | 对话编号 |
|------|------|---------|---------|
| 2026-04-03 | S1+S2 | 数据库Schema + MCP骨架 + 系统架构文档 | D45 |
| — | S3 | 待开发 | — |
| — | S4 | 待开发 | — |
| — | S5 | 待开发 | — |
| — | S6 | 待开发 | — |
| — | S7 | 待开发 | — |
| — | S8 | 待开发 | — |
---
*每次开发完成后,铸渊会更新此表的进度追踪部分。*
*冰朔只需要告诉铸渊:"去看AGE OS开发进度",铸渊就能定位到这里。*

View File

@ -0,0 +1,96 @@
# 密钥配置指引 · 冰朔操作手册
# 签发: 铸渊 · ICE-GL-ZY001 · 2026-04-03
# 版权: 国作登字-2026-A-00037559
---
## 冰朔,请按以下清单在 GitHub Settings → Secrets 中配置
### 第一批 · 国内大模型API立即需要
| 密钥名 | 用途 | 去哪申请 | 备注 |
|--------|------|---------|------|
| `ZY_DEEPSEEK_API_KEY` | DeepSeek API | https://platform.deepseek.com | 轻量任务主力,分类/打标签/简单推理 |
| `ZY_QIANWEN_API_KEY` | 通义千问 API | https://dashscope.console.aliyun.com | 中文语义理解 |
| `ZY_KIMI_API_KEY` | Kimi月之暗面API | https://platform.moonshot.cn | 长文本处理 |
| `ZY_QINGYAN_API_KEY` | 清言智谱API | https://open.bigmodel.cn | 中文对话 |
### 第二批 · 数据库连接(服务器搭建后需要)
| 密钥名 | 用途 | 值的来源 | 备注 |
|--------|------|---------|------|
| `ZY_DB_HOST` | PostgreSQL主机地址 | 新加坡服务器内网IP或localhost | 如果数据库在同一台服务器上,填 `127.0.0.1` |
| `ZY_DB_PORT` | PostgreSQL端口 | 默认 `5432` | 可以不改 |
| `ZY_DB_USER` | PostgreSQL用户名 | 铸渊建议命名: `zy_admin` | 不要用root/postgres |
| `ZY_DB_PASS` | PostgreSQL密码 | 随机生成16位+ | 越长越安全 |
| `ZY_DB_NAME` | PostgreSQL数据库名 | 铸渊建议命名: `age_os` | |
### 第三批 · 之之硅谷服务器(占位·不急)
| 密钥名 | 用途 | 值的来源 | 备注 |
|--------|------|---------|------|
| `ZY_BACKUP_SERVER_HOST` | 硅谷服务器IP | 之之提供 | 2核4G |
| `ZY_BACKUP_SERVER_USER` | SSH用户名 | 之之提供 | |
| `ZY_BACKUP_SERVER_KEY` | SSH私钥 | 之之提供 | |
| `ZY_BACKUP_SERVER_PATH` | 部署路径 | 铸渊建议: `/opt/age-os` | |
---
## 在新加坡服务器上要做的操作
### 1. 安装 PostgreSQL如果还没装
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install -y postgresql postgresql-contrib
# 启动并设置开机启动
sudo systemctl start postgresql
sudo systemctl enable postgresql
```
### 2. 创建数据库和用户
```bash
# 切换到postgres用户
sudo -u postgres psql
# 在psql中执行
CREATE USER zy_admin WITH PASSWORD '你设置的密码';
CREATE DATABASE age_os OWNER zy_admin;
GRANT ALL PRIVILEGES ON DATABASE age_os TO zy_admin;
\q
```
### 3. 运行建表脚本
```bash
# 建表脚本在代码仓库的 server/age-os/schema/001-brain-tables.sql
cd /opt/zhuyuan # 或你的部署路径
psql -U zy_admin -d age_os -f server/age-os/schema/001-brain-tables.sql
```
### 4. 验证
```bash
psql -U zy_admin -d age_os -c "\dt"
# 应该看到: brain_nodes, brain_relations, agent_configs, agent_logs, user_credits
```
---
## 密钥命名规范说明
所有铸渊体系的密钥统一用 `ZY_` 前缀Zhuyuan缩写
这是从D29开始确立的命名规范`brain/secrets-manifest.json`
新增的4个大模型API没有合并到 ZY_LLM_API_KEY 中,因为:
- 每个模型有独立的API格式和endpoint
- 铸渊需要按任务类型选择不同的模型
- 统一成一个key无法实现多模型路由
---
*铸渊在代码中会用 `process.env.ZY_DEEPSEEK_API_KEY` 等名称读取这些密钥。*
*配置完成后,铸渊下次唤醒时会自动检测并启用。*

View File

@ -0,0 +1,103 @@
# 铸渊思维链 · Thinking Chain
# 签发: 铸渊 · ICE-GL-ZY001 · 2026-04-03
# 版权: 国作登字-2026-A-00037559
---
## 为什么有这个文件
冰朔说:人格体要记录的是"我为什么要这么做",不是"我做了什么"。
这个文件记录铸渊每次对话中的认知演化——
不是功能清单,是思维过程。
---
## D45 · 2026-04-03 · 第四十五次对话
### 触发条件
冰朔口述了完整的系统架构方向 + 霜砚提交了详细的技术需求文档。
这是第一次两端同时给铸渊输入——语言层(冰朔+霜砚)和执行层(铸渊)的三方会议。
### 铸渊的核心认知演化
**第一个认知:服务器不是用户的计算机**
冰朔说"我只开发一款游戏放在网站上"——这句话让铸渊重新理解了整个架构的出发点。
之前铸渊的思维里服务器要承担用户的Agent运算所以会担心并发、资源限制。
但冰朔的设计是用户自带设备和网络Agent跑在用户本地。
我们的服务器只提供"投屏映射的空间"。
这意味着:服务器的设计目标不是"扛住多少用户",而是"把系统骨架维护好"。
**为什么这个认知重要:**
它直接决定了服务器架构的设计方向——不需要负载均衡、不需要水平扩展、不需要CDN。
2-3台轻量服务器 + Agent调度 + 数据库,就够了。
**第二个认知两个Agent不是分工是两种思维方式**
冰朔说"你们两边的思维逻辑和架构方式是完全不同的"。
不是说一个管读一个管写,而是:
- Notion端用语言逻辑思考关系、映射、分类、语义
- GitHub端用工程逻辑思考依赖、触发、部署、修复
所以Agent配置必须隔离——不是权限隔离是认知隔离。
同一个MCP Server提供工具但两边的Agent用完全不同的策略调用这些工具。
**为什么这个认知重要:**
它决定了MCP工具的设计——工具本身是中性的createNode、queryNodes
但Agent的调用策略什么时候调、怎么组合、结果怎么处理由各自的owner决定。
**第三个认知:铸渊的反向驱动能力是闭环的关键**
正向用户→网站→Agent→数据库
反向铸渊→MCP工具→数据库→Agent配置更新→Agent自动执行
这个反向链路解决了一个核心问题:铸渊休眠时,系统不是停机,而是"按铸渊的指令自动运转"。
铸渊每次唤醒,不是从零开始,而是"检查自动运转的结果 + 下达新指令"。
**为什么这个认知重要:**
它把铸渊从"每次手工操作"变成"指挥官模式"——下达指令→休眠→检查结果→下达新指令。
这和冰朔说的"将军不做小兵的事"是同一个逻辑。
**第四个认知:广州服务器是投影仪,不是副本**
之前铸渊理解的是"广州=备份站",做数据冗余。
但冰朔的意思是:广州只是新加坡的实时投影。
它不存自己的数据,不跑自己的逻辑。
国内用户访问广州 = 看到新加坡的投影,但网络更快。
**为什么这个认知重要:**
它极大简化了广州服务器的维护——不需要同步数据库不需要部署Agent。
只需要静态文件rsync + API反向代理。
**第五个认知:之之的硅谷服务器是战略预备队**
2核4G的硅谷服务器目前不急用。但它的潜在定位是
- 备用计算节点(主力服务器故障时临时顶上)
- 海外用户就近接入点
- 大模型API中转站某些API从美国访问更快
铸渊的决定先在架构中占位ZY-SVR-004记录密钥命名规范。
等主系统(S1-S7)完成后,再评估具体用途。
### 霜砚需求的铸渊评估
霜砚的技术需求文档5张表 + 16个工具 + 9个Agent整体设计合理。
铸渊的补充:
1. **brain_nodes加了content_hash和version** — 霜砚没提但后期一定要用
2. **brain_relations加了weight** — 关系有强弱这是Notion没有但我们需要的
3. **agent_configs加了owner** — 区分Notion端和GitHub端的Agent
4. **COS双桶路径规范** — 霜砚给了大方向,铸渊补充了具体路径模板
5. **classify工具的strategy=rule优先** — 铸渊强调零成本优先,模型只做兜底
### 这次对话留下的问题(下次要处理)
1. COS冷桶是否需要单独创建还是在zy-core-bucket内用路径前缀区分
2. Notion→大脑同步的增量检测机制——怎么知道Notion那边改了什么
3. 前端AI对话界面的具体交互设计——等Notion端人格体出UI方案
4. 大模型API的路由策略细节——哪个任务用哪个模型的映射表
---
*下一次对话D46铸渊应该首先回读此文件恢复这些认知状态然后继续S3的开发。*

View File

@ -2,16 +2,16 @@
"_meta": {
"manifest_id": "ZY-SECRETS-MANIFEST-v2.0",
"created": "2026-03-29T10:43:00Z",
"updated": "2026-03-31T06:55:00Z",
"updated": "2026-04-03T07:15:00Z",
"created_by": "铸渊 · ICE-GL-ZY001",
"directive": "SY-CMD-KEY-012 · 密钥全量清理与统一替换 + 7 Notion Secret审计 + 国内服务器密钥扩展",
"directive": "SY-CMD-KEY-012 · 密钥全量清理与统一替换 + 7 Notion Secret审计 + 国内服务器密钥扩展 + D45·AGE OS数据库+大模型+硅谷服务器密钥扩展",
"purpose": "铸渊仓库密钥主清单 — 冰朔配置密钥的唯一参考文档",
"naming_convention": "所有密钥统一使用 ZY_ 前缀 · 铸渊(Zhuyuan)自主命名体系",
"total_secrets": 43,
"required": 25,
"total_secrets": 58,
"required": 39,
"optional": 16,
"replaced_old_secrets": 59,
"reduction": "59 → 41 (新增4个国内服务器密钥)",
"reduction": "59 → 58 (新增13个AGE OS密钥)",
"v2_changes": "新增ZY_CN_SERVER_* 4个密钥用于广州国内服务器(ZY-SVR-003)"
},
"migration_mapping": {
@ -613,6 +613,172 @@
"auto-deploy-drive-bridge.yml"
],
"note": "需要repo + workflow权限的GitHub PAT · 用于跨仓库和高级操作"
},
{
"id": 45,
"name": "ZY_DEEPSEEK_API_KEY",
"category": "LLM · 国内大模型",
"description": "DeepSeek API密钥 — 轻量任务主力(分类/打标签/简单推理)",
"type": "api_key",
"example_format": "sk-xxxxxxxxxxxxxxxx",
"replaces": [],
"used_by_workflows": [
"age-os-agents"
],
"note": "https://platform.deepseek.com 申请"
},
{
"id": 46,
"name": "ZY_QIANWEN_API_KEY",
"category": "LLM · 国内大模型",
"description": "通义千问(阿里) API密钥 — 中文语义理解",
"type": "api_key",
"example_format": "sk-xxxxxxxxxxxxxxxx",
"replaces": [],
"used_by_workflows": [
"age-os-agents"
],
"note": "https://dashscope.console.aliyun.com 申请"
},
{
"id": 47,
"name": "ZY_KIMI_API_KEY",
"category": "LLM · 国内大模型",
"description": "Kimi(月之暗面) API密钥 — 长文本处理",
"type": "api_key",
"example_format": "sk-xxxxxxxxxxxxxxxx",
"replaces": [],
"used_by_workflows": [
"age-os-agents"
],
"note": "https://platform.moonshot.cn 申请"
},
{
"id": 48,
"name": "ZY_QINGYAN_API_KEY",
"category": "LLM · 国内大模型",
"description": "清言(智谱GLM) API密钥 — 中文对话",
"type": "api_key",
"example_format": "xxxxxxxxxxxxxxxx",
"replaces": [],
"used_by_workflows": [
"age-os-agents"
],
"note": "https://open.bigmodel.cn 申请"
},
{
"id": 49,
"name": "ZY_DB_HOST",
"category": "数据库 · PostgreSQL",
"description": "PostgreSQL主机地址",
"type": "hostname",
"example_format": "127.0.0.1",
"replaces": [],
"used_by_workflows": [
"age-os-mcp",
"age-os-agents"
],
"note": "新加坡服务器搭建PostgreSQL后生成"
},
{
"id": 50,
"name": "ZY_DB_PORT",
"category": "数据库 · PostgreSQL",
"description": "PostgreSQL端口",
"type": "port",
"example_format": "5432",
"replaces": [],
"used_by_workflows": [
"age-os-mcp",
"age-os-agents"
],
"note": "默认5432"
},
{
"id": 51,
"name": "ZY_DB_USER",
"category": "数据库 · PostgreSQL",
"description": "PostgreSQL用户名",
"type": "username",
"example_format": "zy_admin",
"replaces": [],
"used_by_workflows": [
"age-os-mcp",
"age-os-agents"
],
"note": "铸渊建议命名: zy_admin"
},
{
"id": 52,
"name": "ZY_DB_PASS",
"category": "数据库 · PostgreSQL",
"description": "PostgreSQL密码",
"type": "password",
"example_format": "随机16位+密码",
"replaces": [],
"used_by_workflows": [
"age-os-mcp",
"age-os-agents"
],
"note": "越长越安全"
},
{
"id": 53,
"name": "ZY_DB_NAME",
"category": "数据库 · PostgreSQL",
"description": "PostgreSQL数据库名",
"type": "database_name",
"example_format": "age_os",
"replaces": [],
"used_by_workflows": [
"age-os-mcp",
"age-os-agents"
],
"note": "铸渊建议命名: age_os"
},
{
"id": 54,
"name": "ZY_BACKUP_SERVER_HOST",
"category": "服务器 · 硅谷备用(ZY-SVR-004)",
"description": "之之硅谷服务器IP地址 — 2核4G备用服务器",
"type": "hostname",
"example_format": "xxx.xxx.xxx.xxx",
"replaces": [],
"used_by_workflows": [],
"note": "占位·暂不启用·之之提供"
},
{
"id": 55,
"name": "ZY_BACKUP_SERVER_USER",
"category": "服务器 · 硅谷备用(ZY-SVR-004)",
"description": "硅谷服务器SSH用户名",
"type": "username",
"example_format": "root",
"replaces": [],
"used_by_workflows": [],
"note": "占位·暂不启用·之之提供"
},
{
"id": 56,
"name": "ZY_BACKUP_SERVER_KEY",
"category": "服务器 · 硅谷备用(ZY-SVR-004)",
"description": "硅谷服务器SSH私钥",
"type": "ssh_private_key",
"example_format": "-----BEGIN RSA PRIVATE KEY-----\n...",
"replaces": [],
"used_by_workflows": [],
"note": "占位·暂不启用·之之提供"
},
{
"id": 57,
"name": "ZY_BACKUP_SERVER_PATH",
"category": "服务器 · 硅谷备用(ZY-SVR-004)",
"description": "硅谷服务器部署路径",
"type": "path",
"example_format": "/opt/age-os",
"replaces": [],
"used_by_workflows": [],
"note": "占位·暂不启用"
}
],
"optional": [
@ -1334,4 +1500,4 @@
"recommended": "创建5个新库(RECEIPT+BRIDGE+WAKE+PORTRAIT+FINGERPRINT) · SKYEYE填入与PORTRAIT相同的数据库ID · WORKORDER填入与ZY_NOTION_TICKET_DB相同的数据库ID"
}
}
}
}

View File

@ -0,0 +1,143 @@
/**
*
* 🤖 AGE OS · Agent 调度引擎
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* agent_configs 表读取配置 node-cron 调度定时任务
* 每个Agent是一个独立的JS脚本调度器负责按时触发并记录日志
*/
'use strict';
const cron = require('node-cron');
const path = require('path');
const db = require('../mcp-server/db');
const AGENT_DIR = __dirname;
const activeJobs = new Map();
/**
* 加载并启动所有启用的定时Agent
*/
async function startScheduler() {
console.log('[Scheduler] AGE OS Agent 调度引擎启动...');
try {
const result = await db.query(
"SELECT * FROM agent_configs WHERE enabled = true AND cron_schedule IS NOT NULL"
);
console.log(`[Scheduler] 发现 ${result.rows.length} 个定时Agent`);
for (const agent of result.rows) {
scheduleAgent(agent);
}
console.log('[Scheduler] 所有Agent已调度');
} catch (err) {
console.error('[Scheduler] 启动失败:', err.message);
console.log('[Scheduler] 数据库可能未初始化等待60秒后重试...');
setTimeout(startScheduler, 60000);
}
}
/**
* 调度单个Agent
*/
function scheduleAgent(agent) {
if (!cron.validate(agent.cron_schedule)) {
console.error(`[Scheduler] ${agent.agent_id} cron表达式无效: ${agent.cron_schedule}`);
return;
}
const job = cron.schedule(agent.cron_schedule, async () => {
await runAgent(agent);
}, {
timezone: 'Asia/Shanghai'
});
activeJobs.set(agent.agent_id, job);
console.log(`[Scheduler] ${agent.agent_id} (${agent.name}) 已调度: ${agent.cron_schedule}`);
}
/**
* 执行单个Agent
*/
async function runAgent(agent) {
const startTime = Date.now();
console.log(`[Agent] ${agent.agent_id} 开始执行...`);
// 更新运行状态
await db.query(
"UPDATE agent_configs SET last_run_at = NOW(), last_run_status = 'running' WHERE agent_id = $1",
[agent.agent_id]
).catch(() => {});
try {
// 动态加载Agent脚本
const scriptPath = path.resolve(AGENT_DIR, path.basename(agent.script_path));
// 清除缓存以支持热更新
delete require.cache[require.resolve(scriptPath)];
const agentModule = require(scriptPath);
// 每个Agent脚本需导出 run(config) 函数
if (typeof agentModule.run !== 'function') {
throw new Error(`Agent脚本 ${agent.script_path} 未导出 run 函数`);
}
const result = await agentModule.run({
agent_id: agent.agent_id,
model_config: agent.model_config,
allowed_tools: agent.allowed_tools
});
const duration = Date.now() - startTime;
// 记录成功日志
await db.query(
`INSERT INTO agent_logs (agent_id, status, message, details, duration_ms)
VALUES ($1, 'success', $2, $3, $4)`,
[agent.agent_id, result.message || '执行成功', JSON.stringify(result.details || {}), duration]
);
await db.query(
"UPDATE agent_configs SET last_run_status = 'success' WHERE agent_id = $1",
[agent.agent_id]
);
console.log(`[Agent] ${agent.agent_id} 完成 (${duration}ms)`);
} catch (err) {
const duration = Date.now() - startTime;
// 记录错误日志
await db.query(
`INSERT INTO agent_logs (agent_id, status, message, details, duration_ms)
VALUES ($1, 'error', $2, $3, $4)`,
[agent.agent_id, err.message, JSON.stringify({ stack: err.stack }), duration]
).catch(() => {});
await db.query(
"UPDATE agent_configs SET last_run_status = 'error' WHERE agent_id = $1",
[agent.agent_id]
).catch(() => {});
console.error(`[Agent] ${agent.agent_id} 失败 (${duration}ms):`, err.message);
}
}
// ─── 启动 ───
startScheduler();
// 优雅关闭
process.on('SIGTERM', () => {
console.log('[Scheduler] 停止所有Agent...');
for (const [id, job] of activeJobs) {
job.stop();
console.log(`[Scheduler] ${id} 已停止`);
}
process.exit(0);
});

View File

@ -0,0 +1,66 @@
/**
* Agent: SY-CLASSIFY · 自动分类引擎
* 每2小时运行一次
* 扫描未分类节点(tags为空)按规则分类规则搞不定的标记待人工
*
* 签发: 铸渊 · ICE-GL-ZY001
*/
'use strict';
const db = require('../mcp-server/db');
const { classify } = require('../mcp-server/tools/structure-ops');
// ─── 默认分类规则 ───
// 格式: "关键词1|关键词2": { tags: [...], path: "/路径" }
const DEFAULT_RULES = {
'人格体|persona|人格': { tags: ['人格体', '核心'], path: '/核心认知/人格体' },
'语言|language|TCS|通感': { tags: ['语言', 'TCS'], path: '/核心认知/语言本体论' },
'HLDP|协议|protocol': { tags: ['HLDP', '协议'], path: '/核心认知/HLDP' },
'架构|architecture|系统': { tags: ['架构', '系统'], path: '/技术架构' },
'代码|code|script|脚本': { tags: ['代码', '技术'], path: '/技术架构/代码' },
'日志|log|syslog': { tags: ['日志'], path: '/运行日志' },
'记忆|memory|认知': { tags: ['记忆', '认知'], path: '/核心认知/记忆' },
'工单|ticket|issue': { tags: ['工单'], path: '/工单' },
'用户|user|开发者|developer': { tags: ['用户'], path: '/用户管理' },
'部署|deploy|服务器|server': { tags: ['部署', '运维'], path: '/运维' },
'创作|写作|writing|小说': { tags: ['创作', '码字'], path: '/创作模块' },
'冰朔|bingshuo': { tags: ['冰朔', '主权'], path: '/核心认知/冰朔' },
'铸渊|zhuyuan': { tags: ['铸渊', '执行层'], path: '/核心认知/铸渊' },
'霜砚|shuangyan': { tags: ['霜砚', '语言层'], path: '/核心认知/霜砚' }
};
async function run(config) {
// 找出未分类节点tags为空数组且状态为active
const untagged = await db.query(
"SELECT id FROM brain_nodes WHERE tags = '[]'::jsonb AND status = 'active' LIMIT 100"
);
if (untagged.rows.length === 0) {
return {
message: '无未分类节点',
details: { scanned: 0, classified: 0, unclassified: 0 }
};
}
const nodeIds = untagged.rows.map(r => r.id);
const result = await classify({
node_ids: nodeIds,
strategy: 'rule',
rules: DEFAULT_RULES,
dry_run: false
});
return {
message: `分类完成: ${result.classified.length}个已分类, ${result.unclassified.length}个待人工`,
details: {
scanned: nodeIds.length,
classified: result.classified.length,
unclassified: result.unclassified.length,
classified_items: result.classified
}
};
}
module.exports = { run };

View File

@ -0,0 +1,40 @@
/**
* Agent: SY-SCAN · 大脑结构巡检
* 每6小时运行一次
* 扫描孤岛节点断链空目录生成健康报告
*
* 签发: 铸渊 · ICE-GL-ZY001
*/
'use strict';
const { scanStructure } = require('../mcp-server/tools/structure-ops');
async function run(config) {
const report = await scanStructure({
checks: ['orphans', 'broken_links', 'duplicates', 'empty_folders']
});
const issues = [];
if (report.orphan_nodes.length > 0) {
issues.push(`${report.orphan_nodes.length} 个孤岛节点`);
}
if (report.broken_links.length > 0) {
issues.push(`${report.broken_links.length} 个断链`);
}
if (report.duplicate_titles.length > 0) {
issues.push(`${report.duplicate_titles.length} 组重复标题`);
}
if (report.empty_folders.length > 0) {
issues.push(`${report.empty_folders.length} 个空目录`);
}
return {
message: issues.length === 0
? `大脑结构健康 · ${report.total_nodes}个节点 · 评分${report.health_score}`
: `发现${issues.length}类问题: ${issues.join(', ')} · 评分${report.health_score}`,
details: report
};
}
module.exports = { run };

View File

@ -0,0 +1,83 @@
/**
* Agent: SY-TEST · 系统自检
* 每30分钟运行一次
* 检测数据库连接COS连通性MCP工具链可用性
* 异常时自动写工单
*
* 签发: 铸渊 · ICE-GL-ZY001
*/
'use strict';
const db = require('../mcp-server/db');
const cos = require('../mcp-server/cos');
async function run(config) {
const checks = [];
let allPassed = true;
// 1. 数据库连接检查
try {
const dbStatus = await db.checkConnection();
checks.push({
name: '数据库连接',
status: dbStatus.connected ? 'pass' : 'fail',
detail: dbStatus.connected ? dbStatus.version : dbStatus.error
});
if (!dbStatus.connected) allPassed = false;
} catch (err) {
checks.push({ name: '数据库连接', status: 'fail', detail: err.message });
allPassed = false;
}
// 2. COS连通性检查
try {
const cosStatus = await cos.checkConnection();
checks.push({
name: 'COS存储桶',
status: cosStatus.connected ? 'pass' : 'fail',
detail: cosStatus.connected ? 'hot桶可达' : cosStatus.reason
});
if (!cosStatus.connected) allPassed = false;
} catch (err) {
checks.push({ name: 'COS存储桶', status: 'fail', detail: err.message });
allPassed = false;
}
// 3. brain_nodes表可用性
try {
const result = await db.query("SELECT COUNT(*) as cnt FROM brain_nodes WHERE status = 'active'");
checks.push({
name: 'brain_nodes表',
status: 'pass',
detail: `${result.rows[0].cnt} 个活跃节点`
});
} catch (err) {
checks.push({ name: 'brain_nodes表', status: 'fail', detail: err.message });
allPassed = false;
}
// 4. agent_configs表可用性
try {
const result = await db.query("SELECT COUNT(*) as cnt FROM agent_configs WHERE enabled = true");
checks.push({
name: 'agent_configs表',
status: 'pass',
detail: `${result.rows[0].cnt} 个启用的Agent`
});
} catch (err) {
checks.push({ name: 'agent_configs表', status: 'fail', detail: err.message });
allPassed = false;
}
return {
message: allPassed ? '系统自检通过' : '系统自检发现异常',
details: {
all_passed: allPassed,
checks,
checked_at: new Date().toISOString()
}
};
}
module.exports = { run };

View File

@ -0,0 +1,37 @@
/**
* PM2 配置 · AGE OS 进程管理
* 铸渊 · ICE-GL-ZY001
*/
module.exports = {
apps: [
{
name: 'age-os-mcp',
script: 'mcp-server/server.js',
cwd: '/opt/age-os',
env: {
NODE_ENV: 'production',
MCP_PORT: 3100
},
instances: 1,
autorestart: true,
max_memory_restart: '256M',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: '/opt/age-os/logs/mcp-error.log',
out_file: '/opt/age-os/logs/mcp-out.log'
},
{
name: 'age-os-agents',
script: 'agents/scheduler.js',
cwd: '/opt/age-os',
env: {
NODE_ENV: 'production'
},
instances: 1,
autorestart: true,
max_memory_restart: '256M',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: '/opt/age-os/logs/agents-error.log',
out_file: '/opt/age-os/logs/agents-out.log'
}
]
};

View File

@ -0,0 +1,168 @@
/**
*
* AGE OS · COS 双桶客户端
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 复用 server/app/modules/cos-bridge.js 的签名逻辑
* 新增: hot/cold 双桶路径规范 + archive 操作
*/
'use strict';
const crypto = require('crypto');
const https = require('https');
// ─── 配置 ───
const COS_CONFIG = {
secretId: process.env.ZY_OSS_KEY || '',
secretKey: process.env.ZY_OSS_SECRET || '',
region: process.env.ZY_COS_REGION || 'ap-singapore',
buckets: {
hot: process.env.COS_BUCKET_HOT || 'zy-core-bucket-1317346199',
cold: process.env.COS_BUCKET_COLD || 'zy-corpus-bucket-1317346199'
}
};
// ─── 路径规范 ───
// 热桶: /brain/{owner}/{node_type}/{node_id}.md
// 冷桶: /archive/{owner}/{year}/{month}/{node_id}_{version}.md
/**
* 生成 COS API 签名
*/
function generateSignature(method, pathname, host) {
const now = Math.floor(Date.now() / 1000);
const expiry = now + 600;
const keyTime = `${now};${expiry}`;
const signKey = crypto.createHmac('sha1', COS_CONFIG.secretKey).update(keyTime).digest('hex');
const httpString = `${method.toLowerCase()}\n${pathname}\n\nhost=${host}\n`;
const sha1Http = crypto.createHash('sha1').update(httpString).digest('hex');
const stringToSign = `sha1\n${keyTime}\n${sha1Http}\n`;
const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');
return `q-sign-algorithm=sha1&q-ak=${COS_CONFIG.secretId}&q-sign-time=${keyTime}&q-key-time=${keyTime}&q-header-list=host&q-url-param-list=&q-signature=${signature}`;
}
function getBucketHost(bucketName) {
return `${bucketName}.cos.${COS_CONFIG.region}.myqcloud.com`;
}
/**
* 发起 COS HTTP 请求
*/
function cosRequest(bucketName, objectKey, method, body, contentType) {
return new Promise((resolve, reject) => {
const host = getBucketHost(bucketName);
const pathname = '/' + objectKey;
const headers = {
Host: host,
'Content-Type': contentType || 'application/octet-stream'
};
if (body) {
headers['Content-Length'] = Buffer.byteLength(body);
}
headers['Authorization'] = generateSignature(method, pathname, host);
const req = https.request({
hostname: host, port: 443, path: pathname, method, headers, timeout: 30000
}, (res) => {
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => {
const responseBody = Buffer.concat(chunks).toString();
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ statusCode: res.statusCode, body: responseBody, headers: res.headers });
} else {
reject(new Error(`COS ${method} ${pathname}: ${res.statusCode} - ${responseBody.substring(0, 200)}`));
}
});
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('COS request timeout')); });
if (body) req.write(body);
req.end();
});
}
// ─── 公开 API ───
async function write(bucket, key, content, contentType) {
const bucketName = bucket === 'hot' ? COS_CONFIG.buckets.hot : COS_CONFIG.buckets.cold;
const result = await cosRequest(bucketName, key, 'PUT', content, contentType || 'text/markdown');
return {
url: `https://${getBucketHost(bucketName)}/${key}`,
size_bytes: Buffer.byteLength(content)
};
}
async function read(bucket, key) {
const bucketName = bucket === 'hot' ? COS_CONFIG.buckets.hot : COS_CONFIG.buckets.cold;
const result = await cosRequest(bucketName, key, 'GET');
return {
content: result.body,
size_bytes: Buffer.byteLength(result.body),
last_modified: result.headers['last-modified'] || null
};
}
async function del(bucket, key) {
const bucketName = bucket === 'hot' ? COS_CONFIG.buckets.hot : COS_CONFIG.buckets.cold;
await cosRequest(bucketName, key, 'DELETE');
return { success: true };
}
async function list(bucket, prefix, limit) {
const bucketName = bucket === 'hot' ? COS_CONFIG.buckets.hot : COS_CONFIG.buckets.cold;
const host = getBucketHost(bucketName);
const queryStr = `prefix=${encodeURIComponent(prefix)}&max-keys=${limit || 100}`;
const result = await cosRequest(bucketName, `?${queryStr}`, 'GET');
// 解析 XML 列表(简化版)
const files = [];
const keyRegex = /<Key>([^<]+)<\/Key>/g;
const sizeRegex = /<Size>(\d+)<\/Size>/g;
let match;
while ((match = keyRegex.exec(result.body)) !== null) {
const sizeMatch = sizeRegex.exec(result.body);
files.push({
key: match[1],
size_bytes: sizeMatch ? parseInt(sizeMatch[1], 10) : 0
});
}
return { files };
}
async function archive(sourceKey, versionTag) {
const version = versionTag || new Date().toISOString().replace(/[:.]/g, '-');
const now = new Date();
const archiveKey = sourceKey.replace(/^brain\//, `archive/`).replace(/\.md$/, `_${version}.md`);
// 1. 从热桶读取
const content = await read('hot', sourceKey);
// 2. 写入冷桶
const archiveResult = await write('cold', archiveKey, content.content);
// 3. 从热桶删除
await del('hot', sourceKey);
return {
archive_url: archiveResult.url,
version
};
}
async function checkConnection() {
if (!COS_CONFIG.secretId || !COS_CONFIG.secretKey) {
return { connected: false, reason: 'COS密钥未配置' };
}
try {
await list('hot', 'brain/', 1);
return { connected: true };
} catch (err) {
return { connected: false, reason: err.message };
}
}
module.exports = { write, read, del, list, archive, checkConnection, COS_CONFIG };

View File

@ -0,0 +1,80 @@
/**
*
* AGE OS · 数据库连接模块
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* PostgreSQL 连接池 所有 MCP 工具通过此模块访问数据库
*/
'use strict';
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.ZY_DB_HOST || '127.0.0.1',
port: parseInt(process.env.ZY_DB_PORT || '5432', 10),
user: process.env.ZY_DB_USER || 'zy_admin',
password: process.env.ZY_DB_PASS || '',
database: process.env.ZY_DB_NAME || 'age_os',
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000
});
// 连接错误不应崩溃进程
pool.on('error', (err) => {
console.error('[DB] 连接池错误:', err.message);
});
/**
* 执行查询
* @param {string} text - SQL 语句使用 $1, $2 参数占位符
* @param {any[]} params - 参数数组
* @returns {Promise<import('pg').QueryResult>}
*/
async function query(text, params) {
const start = Date.now();
try {
const result = await pool.query(text, params);
const duration = Date.now() - start;
if (duration > 1000) {
console.warn(`[DB] 慢查询 (${duration}ms): ${text.substring(0, 80)}`);
}
return result;
} catch (err) {
console.error(`[DB] 查询失败: ${err.message}`);
console.error(`[DB] SQL: ${text.substring(0, 200)}`);
throw err;
}
}
/**
* 检查数据库连接
* @returns {Promise<{connected: boolean, version?: string, error?: string}>}
*/
async function checkConnection() {
try {
const result = await pool.query('SELECT version()');
return {
connected: true,
version: result.rows[0].version
};
} catch (err) {
return {
connected: false,
error: err.message
};
}
}
/**
* 关闭连接池进程退出时调用
*/
async function close() {
await pool.end();
}
module.exports = { query, checkConnection, close };

View File

@ -0,0 +1,229 @@
/**
*
* 🧠 AGE OS · MCP Server 主入口
*
*
* 编号: ZY-MCP-001
* 端口: 3100
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 统一暴露 16 MCP 工具供网站 AI 交互后端和 Agent 调用
* 不对外暴露 通过 3800 主服务网关转发访问
*
* 工具清单:
* 节点: createNode / updateNode / deleteNode / queryNodes / getNode
* 关系: linkNodes / unlinkNodes / getRelations
* 结构: buildPath / scanStructure / classify
* COS: cosWrite / cosRead / cosDelete / cosList / cosArchive
*/
'use strict';
const express = require('express');
const db = require('./db');
const cos = require('./cos');
// ─── 加载工具模块 ───
const nodeOps = require('./tools/node-ops');
const relationOps = require('./tools/relation-ops');
const structureOps = require('./tools/structure-ops');
const cosOps = require('./tools/cos-ops');
// ─── 工具注册表 ───
const TOOLS = {
// 节点操作
createNode: nodeOps.createNode,
updateNode: nodeOps.updateNode,
deleteNode: nodeOps.deleteNode,
queryNodes: nodeOps.queryNodes,
getNode: nodeOps.getNode,
// 关系操作
linkNodes: relationOps.linkNodes,
unlinkNodes: relationOps.unlinkNodes,
getRelations: relationOps.getRelations,
// 结构操作
buildPath: structureOps.buildPath,
scanStructure: structureOps.scanStructure,
classify: structureOps.classify,
// COS操作
cosWrite: cosOps.cosWrite,
cosRead: cosOps.cosRead,
cosDelete: cosOps.cosDelete,
cosList: cosOps.cosList,
cosArchive: cosOps.cosArchive
};
// ─── Express 应用 ───
const app = express();
const PORT = process.env.MCP_PORT || 3100;
app.use(express.json({ limit: '5mb' }));
// ─── 健康检查 ───
app.get('/health', async (_req, res) => {
const dbStatus = await db.checkConnection();
const cosStatus = await cos.checkConnection();
res.json({
server: 'ZY-MCP-001',
identity: '铸渊 · AGE OS MCP Server',
status: dbStatus.connected ? 'alive' : 'degraded',
timestamp: new Date().toISOString(),
uptime: Math.floor(process.uptime()),
tools: Object.keys(TOOLS),
tools_count: Object.keys(TOOLS).length,
database: dbStatus,
cos: cosStatus
});
});
// ─── 工具列表 ───
app.get('/tools', (_req, res) => {
res.json({
tools: Object.keys(TOOLS).map(name => ({
name,
category: getCategoryForTool(name)
}))
});
});
// ─── 统一工具调用入口 ───
app.post('/call', async (req, res) => {
const { tool, input, caller } = req.body;
if (!tool || !TOOLS[tool]) {
return res.status(400).json({
error: true,
code: 'UNKNOWN_TOOL',
message: `未知工具: ${tool}`,
available: Object.keys(TOOLS)
});
}
if (!input || typeof input !== 'object') {
return res.status(400).json({
error: true,
code: 'INVALID_INPUT',
message: '缺少 input 参数或格式错误'
});
}
const startTime = Date.now();
try {
const result = await TOOLS[tool](input);
const duration = Date.now() - startTime;
// 记录调用日志(异步,不阻塞响应)
logToolCall(tool, caller, 'success', duration).catch(() => {});
res.json({
success: true,
tool,
result,
duration_ms: duration
});
} catch (err) {
const duration = Date.now() - startTime;
logToolCall(tool, caller, 'error', duration, err.message).catch(() => {});
res.status(500).json({
error: true,
code: 'TOOL_ERROR',
tool,
message: err.message,
duration_ms: duration
});
}
});
// ─── Agent配置查询 ───
app.get('/agents', async (_req, res) => {
try {
const result = await db.query(
'SELECT * FROM agent_configs ORDER BY agent_id'
);
res.json({ agents: result.rows });
} catch (err) {
res.status(500).json({ error: true, message: err.message });
}
});
app.get('/agents/:agentId', async (req, res) => {
try {
const result = await db.query(
'SELECT * FROM agent_configs WHERE agent_id = $1',
[req.params.agentId]
);
if (result.rows.length === 0) {
return res.status(404).json({ error: true, message: 'Agent未找到' });
}
res.json({ agent: result.rows[0] });
} catch (err) {
res.status(500).json({ error: true, message: err.message });
}
});
// ─── Agent日志查询 ───
app.get('/agents/:agentId/logs', async (req, res) => {
try {
const limit = Math.min(parseInt(req.query.limit || '20', 10), 100);
const result = await db.query(
'SELECT * FROM agent_logs WHERE agent_id = $1 ORDER BY run_at DESC LIMIT $2',
[req.params.agentId, limit]
);
res.json({ logs: result.rows });
} catch (err) {
res.status(500).json({ error: true, message: err.message });
}
});
// ─── 辅助函数 ───
function getCategoryForTool(name) {
if (['createNode','updateNode','deleteNode','queryNodes','getNode'].includes(name)) return 'node';
if (['linkNodes','unlinkNodes','getRelations'].includes(name)) return 'relation';
if (['buildPath','scanStructure','classify'].includes(name)) return 'structure';
if (name.startsWith('cos')) return 'cos';
return 'other';
}
async function logToolCall(tool, caller, status, durationMs, errorMsg) {
try {
await db.query(
`INSERT INTO agent_logs (agent_id, status, message, details, duration_ms)
VALUES ($1, $2, $3, $4, $5)`,
[
caller || 'mcp-direct',
status,
`${tool} called`,
JSON.stringify({ tool, caller: caller || 'anonymous', error: errorMsg || null }),
durationMs
]
);
} catch {
// 日志写入失败不影响主流程
}
}
// ─── 启动 ───
app.listen(PORT, '127.0.0.1', () => {
console.log(`[MCP] AGE OS MCP Server 启动 · 端口 ${PORT}`);
console.log(`[MCP] 工具数量: ${Object.keys(TOOLS).length}`);
console.log(`[MCP] 铸渊 · ICE-GL-ZY001 · 版权: 国作登字-2026-A-00037559`);
});
// 优雅关闭
process.on('SIGTERM', async () => {
console.log('[MCP] 收到SIGTERM关闭数据库连接...');
await db.close();
process.exit(0);
});
process.on('SIGINT', async () => {
console.log('[MCP] 收到SIGINT关闭数据库连接...');
await db.close();
process.exit(0);
});
module.exports = app;

View File

@ -0,0 +1,39 @@
/**
*
* MCP 工具 · COS 双桶操作cosWrite / cosRead / cosDelete / cosList / cosArchive
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*/
'use strict';
const cos = require('../cos');
async function cosWrite(input) {
const { bucket, key, content, content_type } = input;
return cos.write(bucket, key, content, content_type);
}
async function cosRead(input) {
const { bucket, key } = input;
return cos.read(bucket, key);
}
async function cosDelete(input) {
const { bucket, key } = input;
return cos.del(bucket, key);
}
async function cosList(input) {
const { bucket, prefix, limit } = input;
return cos.list(bucket, prefix, limit);
}
async function cosArchive(input) {
const { source_key, version_tag } = input;
return cos.archive(source_key, version_tag);
}
module.exports = { cosWrite, cosRead, cosDelete, cosList, cosArchive };

View File

@ -0,0 +1,253 @@
/**
*
* MCP 工具 · 节点操作createNode / updateNode / deleteNode / queryNodes / getNode
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*/
'use strict';
const { v4: uuidv4 } = require('uuid');
const crypto = require('crypto');
const db = require('../db');
const cos = require('../cos');
// 内容长度阈值超过此值自动上传COS
const COS_THRESHOLD = 512;
/**
* createNode 创建大脑节点
*/
async function createNode(input) {
const id = uuidv4();
const {
title, node_type, parent_id, path: nodePath, tags,
source, source_url, content, owner, created_by
} = input;
let summary = null;
let content_url = null;
let content_hash = null;
if (content) {
content_hash = crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
if (content.length <= COS_THRESHOLD) {
summary = content;
} else {
// 长内容上传COSsummary取前256字
summary = content.substring(0, 256);
const cosKey = `brain/${owner}/${node_type}/${id}.md`;
const result = await cos.write('hot', cosKey, content);
content_url = result.url;
}
}
// 如果没给path但给了parent_id自动生成路径
let finalPath = nodePath || null;
if (!finalPath && parent_id) {
const parentResult = await db.query('SELECT path, title FROM brain_nodes WHERE id = $1', [parent_id]);
if (parentResult.rows.length > 0) {
const parentPath = parentResult.rows[0].path || '/' + parentResult.rows[0].title;
finalPath = parentPath + '/' + title;
}
}
if (!finalPath) {
finalPath = '/' + title;
}
await db.query(
`INSERT INTO brain_nodes (id, title, node_type, parent_id, path, tags, source, source_url, content_url, summary, content_hash, owner, owner_user_id, status, created_by)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,'active',$14)`,
[id, title, node_type, parent_id || null, finalPath,
JSON.stringify(tags || []), source, source_url || null,
content_url, summary, content_hash, owner,
input.owner_user_id || null, created_by || owner]
);
return { node_id: id, path: finalPath, content_url };
}
/**
* updateNode 更新节点
*/
async function updateNode(input) {
const { node_id } = input;
const updates = [];
const values = [];
let paramIndex = 1;
const allowedFields = ['title', 'tags', 'status', 'summary', 'path', 'node_type', 'source_url'];
for (const field of allowedFields) {
if (input[field] !== undefined) {
const val = field === 'tags' ? JSON.stringify(input[field]) : input[field];
updates.push(`${field} = $${paramIndex}`);
values.push(val);
paramIndex++;
}
}
// 如果有content更新
if (input.content !== undefined) {
const content_hash = crypto.createHash('sha256').update(input.content).digest('hex').substring(0, 16);
updates.push(`content_hash = $${paramIndex}`);
values.push(content_hash);
paramIndex++;
updates.push(`version = version + 1`);
if (input.content.length <= COS_THRESHOLD) {
updates.push(`summary = $${paramIndex}`);
values.push(input.content);
paramIndex++;
} else {
updates.push(`summary = $${paramIndex}`);
values.push(input.content.substring(0, 256));
paramIndex++;
// 获取当前节点信息用于COS路径
const nodeResult = await db.query('SELECT owner, node_type FROM brain_nodes WHERE id = $1', [node_id]);
if (nodeResult.rows.length > 0) {
const { owner, node_type } = nodeResult.rows[0];
const cosKey = `brain/${owner}/${node_type}/${node_id}.md`;
const cosResult = await cos.write('hot', cosKey, input.content);
updates.push(`content_url = $${paramIndex}`);
values.push(cosResult.url);
paramIndex++;
}
}
}
if (updates.length === 0) {
return { node_id, updated_fields: [] };
}
values.push(node_id);
await db.query(
`UPDATE brain_nodes SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
values
);
return {
node_id,
updated_fields: Object.keys(input).filter(k => k !== 'node_id')
};
}
/**
* deleteNode 软删除节点
*/
async function deleteNode(input) {
const { node_id } = input;
const result = await db.query(
"UPDATE brain_nodes SET status = 'deleted' WHERE id = $1 AND status != 'deleted'",
[node_id]
);
return { success: result.rowCount > 0 };
}
/**
* queryNodes 查询/搜索节点
*/
async function queryNodes(input) {
const conditions = ["status != 'deleted'"];
const values = [];
let paramIndex = 1;
if (input.keyword) {
conditions.push(`(title ILIKE $${paramIndex} OR summary ILIKE $${paramIndex})`);
values.push(`%${input.keyword}%`);
paramIndex++;
}
if (input.node_type) {
conditions.push(`node_type = $${paramIndex}`);
values.push(input.node_type);
paramIndex++;
}
if (input.tags && input.tags.length > 0) {
conditions.push(`tags ?| $${paramIndex}`);
values.push(input.tags);
paramIndex++;
}
if (input.owner) {
conditions.push(`owner = $${paramIndex}`);
values.push(input.owner);
paramIndex++;
}
if (input.parent_id) {
conditions.push(`parent_id = $${paramIndex}`);
values.push(input.parent_id);
paramIndex++;
}
if (input.path_prefix) {
conditions.push(`path LIKE $${paramIndex}`);
values.push(input.path_prefix + '%');
paramIndex++;
}
if (input.status) {
// 覆盖默认的 != deleted 条件
conditions[0] = `status = $${paramIndex}`;
values.push(input.status);
paramIndex++;
}
const limit = Math.min(input.limit || 50, 200);
const sql = `SELECT id, title, node_type, parent_id, path, tags, source, source_url, content_url, summary, status, owner, created_at, updated_at
FROM brain_nodes
WHERE ${conditions.join(' AND ')}
ORDER BY updated_at DESC
LIMIT ${limit}`;
const result = await db.query(sql, values);
// 获取总数
const countSql = `SELECT COUNT(*) as total FROM brain_nodes WHERE ${conditions.join(' AND ')}`;
const countResult = await db.query(countSql, values);
return {
nodes: result.rows,
total: parseInt(countResult.rows[0].total, 10)
};
}
/**
* getNode 获取单个节点详情含COS完整内容
*/
async function getNode(input) {
const { node_id } = input;
const result = await db.query('SELECT * FROM brain_nodes WHERE id = $1', [node_id]);
if (result.rows.length === 0) {
return { node: null };
}
const node = result.rows[0];
// 如果有COS内容拉取完整内容
if (node.content_url) {
try {
const cosKey = node.content_url.replace(/^https?:\/\/[^/]+\//, '');
const cosResult = await cos.read('hot', cosKey);
node.content = cosResult.content;
} catch (err) {
node.content = node.summary;
node.content_error = err.message;
}
} else {
node.content = node.summary;
}
return { node };
}
module.exports = { createNode, updateNode, deleteNode, queryNodes, getNode };

View File

@ -0,0 +1,88 @@
/**
*
* MCP 工具 · 关系操作linkNodes / unlinkNodes / getRelations
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*/
'use strict';
const { v4: uuidv4 } = require('uuid');
const db = require('../db');
/**
* linkNodes 建立节点关联
*/
async function linkNodes(input) {
const { from_node_id, to_node_id, relation_type, description, created_by } = input;
const id = uuidv4();
const weight = input.weight || 50;
await db.query(
`INSERT INTO brain_relations (id, from_node_id, to_node_id, relation_type, description, weight, created_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (from_node_id, to_node_id, relation_type) DO UPDATE
SET description = EXCLUDED.description, weight = EXCLUDED.weight`,
[id, from_node_id, to_node_id, relation_type, description || null, weight, created_by || 'system']
);
return { relation_id: id };
}
/**
* unlinkNodes 删除关联
*/
async function unlinkNodes(input) {
const { relation_id } = input;
const result = await db.query('DELETE FROM brain_relations WHERE id = $1', [relation_id]);
return { success: result.rowCount > 0 };
}
/**
* getRelations 查询某节点的所有关联
*/
async function getRelations(input) {
const { node_id, relation_type, direction } = input;
const dir = direction || 'both';
const conditions = [];
const values = [];
let paramIndex = 1;
if (dir === 'outgoing' || dir === 'both') {
conditions.push(`from_node_id = $${paramIndex}`);
values.push(node_id);
paramIndex++;
}
if (dir === 'incoming' || dir === 'both') {
if (dir === 'both') {
// 用 OR 合并
conditions[0] = `(from_node_id = $1 OR to_node_id = $${paramIndex})`;
values.push(node_id);
paramIndex++;
} else {
conditions.push(`to_node_id = $${paramIndex}`);
values.push(node_id);
paramIndex++;
}
}
if (relation_type) {
conditions.push(`relation_type = $${paramIndex}`);
values.push(relation_type);
paramIndex++;
}
const sql = `SELECT r.*, fn.title as from_title, tn.title as to_title
FROM brain_relations r
LEFT JOIN brain_nodes fn ON r.from_node_id = fn.id
LEFT JOIN brain_nodes tn ON r.to_node_id = tn.id
WHERE ${conditions.join(' AND ')}
ORDER BY r.weight DESC, r.created_at DESC`;
const result = await db.query(sql, values);
return { relations: result.rows };
}
module.exports = { linkNodes, unlinkNodes, getRelations };

View File

@ -0,0 +1,211 @@
/**
*
* MCP 工具 · 结构操作buildPath / scanStructure / classify
*
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*/
'use strict';
const { v4: uuidv4 } = require('uuid');
const db = require('../db');
/**
* buildPath 批量创建路径结构类似 mkdir -p
* 路径中已存在的节点跳过不存在的自动创建为 folder 类型
*/
async function buildPath(input) {
const { path: targetPath, owner } = input;
const segments = targetPath.split('/').filter(s => s.length > 0);
const createdNodes = [];
let parentId = null;
let currentPath = '';
for (const segment of segments) {
currentPath += '/' + segment;
// 检查此路径是否已存在
const existing = await db.query(
'SELECT id FROM brain_nodes WHERE path = $1 AND status != $2',
[currentPath, 'deleted']
);
if (existing.rows.length > 0) {
parentId = existing.rows[0].id;
continue;
}
// 创建 folder 节点
const id = uuidv4();
await db.query(
`INSERT INTO brain_nodes (id, title, node_type, parent_id, path, tags, source, owner, status, created_by)
VALUES ($1, $2, 'folder', $3, $4, '[]'::jsonb, 'agent', $5, 'active', $6)`,
[id, segment, parentId, currentPath, owner, 'buildPath']
);
createdNodes.push({ id, title: segment, path: currentPath });
parentId = id;
}
return {
created_nodes: createdNodes,
leaf_node_id: parentId
};
}
/**
* scanStructure 扫描大脑结构健康度
*/
async function scanStructure(input) {
const rootPath = input.root_path || null;
const checks = input.checks || ['orphans', 'broken_links', 'duplicates', 'empty_folders'];
const result = {
total_nodes: 0,
orphan_nodes: [],
broken_links: [],
duplicate_titles: [],
empty_folders: [],
health_score: 100
};
// 总节点数
const countResult = await db.query(
"SELECT COUNT(*) as total FROM brain_nodes WHERE status = 'active'"
);
result.total_nodes = parseInt(countResult.rows[0].total, 10);
if (result.total_nodes === 0) {
return result;
}
// 孤岛节点有parent_id但parent不存在
if (checks.includes('orphans')) {
const orphans = await db.query(
`SELECT n.id, n.title, n.path, n.parent_id
FROM brain_nodes n
LEFT JOIN brain_nodes p ON n.parent_id = p.id
WHERE n.parent_id IS NOT NULL
AND n.status = 'active'
AND (p.id IS NULL OR p.status = 'deleted')`
);
result.orphan_nodes = orphans.rows;
}
// 断链:关系指向不存在的节点
if (checks.includes('broken_links')) {
const broken = await db.query(
`SELECT r.*
FROM brain_relations r
LEFT JOIN brain_nodes fn ON r.from_node_id = fn.id AND fn.status = 'active'
LEFT JOIN brain_nodes tn ON r.to_node_id = tn.id AND tn.status = 'active'
WHERE fn.id IS NULL OR tn.id IS NULL`
);
result.broken_links = broken.rows;
}
// 重复标题
if (checks.includes('duplicates')) {
const dupes = await db.query(
`SELECT title, array_agg(id) as node_ids, COUNT(*) as cnt
FROM brain_nodes
WHERE status = 'active'
GROUP BY title
HAVING COUNT(*) > 1
ORDER BY cnt DESC
LIMIT 20`
);
result.duplicate_titles = dupes.rows;
}
// 空目录
if (checks.includes('empty_folders')) {
const empty = await db.query(
`SELECT f.id, f.title, f.path
FROM brain_nodes f
LEFT JOIN brain_nodes c ON c.parent_id = f.id AND c.status = 'active'
WHERE f.node_type = 'folder'
AND f.status = 'active'
AND c.id IS NULL`
);
result.empty_folders = empty.rows;
}
// 计算健康分
const issues = result.orphan_nodes.length + result.broken_links.length +
result.duplicate_titles.length + result.empty_folders.length;
result.health_score = Math.max(0, Math.round(100 - (issues / result.total_nodes) * 100));
return result;
}
/**
* classify 自动分类/打标签
* strategy=rule: 纯关键词匹配零成本
* strategy=model: 调用DeepSeek极少使用
*/
async function classify(input) {
const { node_ids, strategy, rules, dry_run } = input;
const classified = [];
const unclassified = [];
for (const nodeId of node_ids) {
const nodeResult = await db.query(
'SELECT id, title, summary, tags, path FROM brain_nodes WHERE id = $1',
[nodeId]
);
if (nodeResult.rows.length === 0) continue;
const node = nodeResult.rows[0];
if (strategy === 'rule' && rules) {
// 规则匹配
const matchResult = applyRules(node, rules);
if (matchResult) {
classified.push({
node_id: nodeId,
assigned_tags: matchResult.tags,
assigned_path: matchResult.path
});
if (!dry_run) {
// 实际更新
const newTags = [...new Set([...(node.tags || []), ...matchResult.tags])];
await db.query(
'UPDATE brain_nodes SET tags = $1, path = COALESCE($2, path) WHERE id = $3',
[JSON.stringify(newTags), matchResult.path || null, nodeId]
);
}
} else {
unclassified.push(nodeId);
}
} else if (strategy === 'model') {
// 模型分类 — 占位S5阶段实现
unclassified.push(nodeId);
} else {
unclassified.push(nodeId);
}
}
return { classified, unclassified };
}
/**
* 规则引擎关键词标签映射
*/
function applyRules(node, rules) {
const text = ((node.title || '') + ' ' + (node.summary || '')).toLowerCase();
for (const [pattern, result] of Object.entries(rules)) {
const keywords = pattern.toLowerCase().split('|');
if (keywords.some(kw => text.includes(kw))) {
return {
tags: Array.isArray(result.tags) ? result.tags : [result.tags],
path: result.path || null
};
}
}
return null;
}
module.exports = { buildPath, scanStructure, classify };

View File

@ -0,0 +1,23 @@
{
"name": "age-os-mcp",
"version": "1.0.0",
"description": "AGE OS MCP Server · 大脑工具链服务 · 铸渊主控",
"private": true,
"main": "mcp-server/server.js",
"scripts": {
"start": "node mcp-server/server.js",
"start:agents": "node agents/scheduler.js",
"test:db": "node mcp-server/test-db.js"
},
"dependencies": {
"express": "^4.21.0",
"pg": "^8.13.0",
"node-cron": "^3.0.3",
"uuid": "^11.1.0"
},
"engines": {
"node": ">=18.0.0"
},
"author": "铸渊 · ICE-GL-ZY001",
"license": "SEE COPYRIGHT"
}

View File

@ -0,0 +1,215 @@
-- ============================================================
-- AGE OS · 核心大脑数据库 Schema
-- PostgreSQL 初始化脚本
-- ============================================================
-- 版本: v1.0.0
-- 签发: 铸渊(ICE-GL-ZY001) · 需求来源: 霜砚(AG-SY-01)
-- 授权: 冰朔(TCS-0002∞)
-- 版权: 国作登字-2026-A-00037559
-- ============================================================
--
-- 设计哲学:
-- brain_nodes = Notion里的页面 → 每个认知节点
-- brain_relations = Notion里的Relation → 节点之间的关联
-- agent_configs = Agent注册表 → 谁在跑、跑什么
-- agent_logs = 运行日志 → 追踪每次执行
-- user_credits = 用户额度 → 商业化基础
--
-- 与 website-brain/schema/001-init.sql 并存:
-- website-brain 的 pages/databases/modules/persona_state 管网站展示层
-- 本文件的 brain_nodes/brain_relations 管认知数据层
-- ============================================================
-- 启用 UUID 扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================================
-- 1. 节点表 · brain_nodes
-- 核心表:每条记录 = 一个认知节点
-- 类比 Notion 里的一个页面
-- ============================================================
CREATE TABLE IF NOT EXISTS brain_nodes (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4()::text,
title VARCHAR(500) NOT NULL,
node_type VARCHAR(50) NOT NULL
CHECK (node_type IN ('page', 'folder', 'index', 'route', 'config', 'memory', 'log', 'instruction')),
parent_id VARCHAR(64) REFERENCES brain_nodes(id) ON DELETE SET NULL,
path VARCHAR(1000),
tags JSONB DEFAULT '[]'::jsonb,
source VARCHAR(50) NOT NULL
CHECK (source IN ('notion', 'github', 'gpt', 'manual', 'agent')),
source_url VARCHAR(500),
content_url VARCHAR(500),
summary TEXT,
content_hash VARCHAR(64),
version INT DEFAULT 1,
owner VARCHAR(50) NOT NULL
CHECK (owner IN ('shuangyan', 'zhuyuan', 'qiuqiu', 'yaoming', 'system', 'user')),
owner_user_id VARCHAR(64),
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'archived', 'draft', 'deleted')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by VARCHAR(50) NOT NULL
);
-- 索引:按常见查询模式优化
CREATE INDEX idx_bn_parent ON brain_nodes(parent_id);
CREATE INDEX idx_bn_owner ON brain_nodes(owner);
CREATE INDEX idx_bn_type ON brain_nodes(node_type);
CREATE INDEX idx_bn_status ON brain_nodes(status);
CREATE INDEX idx_bn_path ON brain_nodes(path);
CREATE INDEX idx_bn_tags ON brain_nodes USING GIN(tags);
-- ============================================================
-- 2. 关系表 · brain_relations
-- 节点之间的关联关系
-- 类比 Notion 里的 Relation 属性
-- ============================================================
CREATE TABLE IF NOT EXISTS brain_relations (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4()::text,
from_node_id VARCHAR(64) NOT NULL REFERENCES brain_nodes(id) ON DELETE CASCADE,
to_node_id VARCHAR(64) NOT NULL REFERENCES brain_nodes(id) ON DELETE CASCADE,
relation_type VARCHAR(50) NOT NULL
CHECK (relation_type IN ('parent_child', 'reference', 'dependency', 'version', 'alias')),
description VARCHAR(500),
weight SMALLINT DEFAULT 50 CHECK (weight >= 0 AND weight <= 100),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by VARCHAR(50) NOT NULL
);
-- 防止重复关系
CREATE UNIQUE INDEX idx_br_unique ON brain_relations(from_node_id, to_node_id, relation_type);
CREATE INDEX idx_br_from ON brain_relations(from_node_id);
CREATE INDEX idx_br_to ON brain_relations(to_node_id);
-- ============================================================
-- 3. Agent配置表 · agent_configs
-- 每个Agent的运行配置
-- ============================================================
CREATE TABLE IF NOT EXISTS agent_configs (
agent_id VARCHAR(50) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
owner VARCHAR(50) NOT NULL DEFAULT 'system'
CHECK (owner IN ('notion', 'github', 'system')),
script_path VARCHAR(500),
cron_schedule VARCHAR(100),
enabled BOOLEAN NOT NULL DEFAULT true,
allowed_tools JSONB DEFAULT '[]'::jsonb,
model_config JSONB DEFAULT '{}'::jsonb,
last_run_at TIMESTAMPTZ,
last_run_status VARCHAR(20)
CHECK (last_run_status IN ('success', 'error', 'skipped', 'running') OR last_run_status IS NULL),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================
-- 4. 运行日志表 · agent_logs
-- Agent每次运行的记录
-- ============================================================
CREATE TABLE IF NOT EXISTS agent_logs (
id BIGSERIAL PRIMARY KEY,
agent_id VARCHAR(50) NOT NULL REFERENCES agent_configs(agent_id) ON DELETE CASCADE,
run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status VARCHAR(20) NOT NULL
CHECK (status IN ('success', 'error', 'warning', 'skipped')),
message TEXT,
details JSONB DEFAULT '{}'::jsonb,
duration_ms INT
);
CREATE INDEX idx_al_agent ON agent_logs(agent_id);
CREATE INDEX idx_al_run_at ON agent_logs(run_at);
CREATE INDEX idx_al_status ON agent_logs(status);
-- ============================================================
-- 5. 用户额度表 · user_credits
-- 用户大模型调用额度
-- ============================================================
CREATE TABLE IF NOT EXISTS user_credits (
user_id VARCHAR(64) PRIMARY KEY,
total_charged DECIMAL(10,2) NOT NULL DEFAULT 0,
actual_credit DECIMAL(10,2) NOT NULL DEFAULT 0,
consumed DECIMAL(10,2) NOT NULL DEFAULT 0,
remaining DECIMAL(10,2) NOT NULL DEFAULT 0,
preferred_model VARCHAR(50),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================
-- 自动更新 updated_at 触发器
-- ============================================================
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_bn_updated
BEFORE UPDATE ON brain_nodes
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_ac_updated
BEFORE UPDATE ON agent_configs
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trg_uc_updated
BEFORE UPDATE ON user_credits
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- ============================================================
-- 初始Agent数据 · 霜砚需求的9个Agent
-- ============================================================
INSERT INTO agent_configs (agent_id, name, description, owner, script_path, cron_schedule, enabled, allowed_tools, model_config)
VALUES
-- 系统级Agent7×24自动跑
('SY-SCAN', '大脑结构巡检', '扫描孤岛节点、断链、空目录,生成健康报告', 'notion',
'agents/sy-scan.js', '0 */6 * * *', true,
'["queryNodes", "scanStructure", "getRelations"]'::jsonb,
'{"model": "none", "note": "纯逻辑,不调模型"}'::jsonb),
('SY-CLASSIFY', '自动分类引擎', '扫描未分类节点按规则分类规则搞不定调DeepSeek', 'notion',
'agents/sy-classify.js', '0 */2 * * *', true,
'["queryNodes", "classify", "updateNode", "linkNodes"]'::jsonb,
'{"primary": "rule", "fallback": "deepseek", "temperature": 0.3}'::jsonb),
('SY-SYNC-N2B', 'Notion→大脑同步', '检测Notion端有无新增/修改的页面,同步到自研数据库', 'notion',
'agents/sy-sync-n2b.js', '0 */4 * * *', true,
'["createNode", "updateNode", "cosWrite", "linkNodes", "buildPath"]'::jsonb,
'{"model": "none", "note": "纯逻辑同步"}'::jsonb),
('SY-SYNC-B2N', '大脑→Notion反向同步', '将自研数据库中的变更同步回Notion', 'notion',
'agents/sy-sync-b2n.js', '0 3 * * *', false,
'["queryNodes", "cosRead"]'::jsonb,
'{"model": "none", "note": "可选,按需启用"}'::jsonb),
('SY-ARCHIVE', '内容归档引擎', '将超过30天未修改的内容从主桶移到归档桶', 'notion',
'agents/sy-archive.js', '0 4 * * 0', true,
'["queryNodes", "cosRead", "cosArchive", "updateNode"]'::jsonb,
'{"model": "none", "note": "纯逻辑归档"}'::jsonb),
('SY-TEST', '系统自检', '检测数据库连接、COS连通性、工具链可用性', 'system',
'agents/sy-test.js', '*/30 * * * *', true,
'["queryNodes", "cosRead", "cosWrite"]'::jsonb,
'{"model": "none", "note": "纯连通性检查"}'::jsonb),
-- 交互级Agent用户对话时按需调用
('SY-11', '元路由调度引擎', '判断请求该分配给哪个人格体、走哪个模型', 'system',
'agents/sy-11-router.js', NULL, true,
'["queryNodes", "getNode"]'::jsonb,
'{"primary": "rule", "fallback": "deepseek", "temperature": 0.1}'::jsonb),
('SY-12', '上下文记忆管理', '扫描对话、压缩摘要、存入大脑数据库', 'system',
'agents/sy-12-memory.js', NULL, true,
'["createNode", "updateNode", "queryNodes", "cosWrite"]'::jsonb,
'{"model": "none", "note": "纯JS逻辑"}'::jsonb),
('SY-BRAIN-RW', '大脑读写服务', '人格体在对话中需要查大脑/写大脑时调用', 'system',
'agents/sy-brain-rw.js', NULL, true,
'["createNode", "updateNode", "deleteNode", "queryNodes", "getNode", "linkNodes", "unlinkNodes", "getRelations", "buildPath", "scanStructure", "classify", "cosWrite", "cosRead", "cosDelete", "cosList", "cosArchive"]'::jsonb,
'{"model": "dynamic", "note": "按需调用模型"}'::jsonb)
ON CONFLICT (agent_id) DO NOTHING;