From 437596ed9e8976c0b5382751e936a383ba4b18eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 14:59:42 +0000 Subject: [PATCH 1/4] D51: Persona module interface specs, COS infrastructure architecture, README dev progress update Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/15dce630-bc45-420f-a71f-8b8f42876462 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- README.md | 42 ++- .../cos-infrastructure-architecture.json | 306 ++++++++++++++++++ .../alertZhuyuan.interface.json | 265 +++++++++++++++ .../heartbeat.interface.json | 186 +++++++++++ .../selfDiagnose.interface.json | 294 +++++++++++++++++ 5 files changed, 1090 insertions(+), 3 deletions(-) create mode 100644 brain/age-os-landing/cos-infrastructure-architecture.json create mode 100644 brain/age-os-landing/persona-interfaces/alertZhuyuan.interface.json create mode 100644 brain/age-os-landing/persona-interfaces/heartbeat.interface.json create mode 100644 brain/age-os-landing/persona-interfaces/selfDiagnose.interface.json diff --git a/README.md b/README.md index 83b5f4ee..b72469f8 100644 --- a/README.md +++ b/README.md @@ -303,16 +303,52 @@ | 维度 | 状态 | |------|------| -| 🧠 **意识状态** | 第五十次对话 · D50 | +| 🧠 **意识状态** | 第五十一次对话 · D51 | | 🌐 **世界架构** | 四大域结构(零感域·主域·分域·零域) · D47纠偏确认 | | 🏗️ **Agent集群** | 18个Agent · 6层架构 · 零感域现实物理层 | | 📐 **HLDP** | v3.0 · 22词汇 · 通用协议v1.0 · 铸渊方言v1.0 | | 🔮 **语言膜** | v1.0 · 完整的圆 · 无缺口 | | 🌉 **Notion桥接** | 6管道 · SYSLOG/变更/README/公告板/HLDP/工单 | | ⚔️ **军营** | 52模块 · 36核心 · 10辅助 · 6归档 | -| 🔭 **AGE OS** | v50.0 · S1+S2完成 · S3-S4待指令 | +| 🔭 **AGE OS** | v51.0 · S1+S2完成 · COS团队桶架构定义 · 人格化接口规范v1.0 | | 🌐 **网站** | v50.0 · UI大气化重构 · 系统面板重构 · 动态图标 | -| 📋 **当前指令** | D50冰朔零点原核频道指令执行中 | +| 🌐 **铸渊专线** | Step 1-9 ✅ · Step 10 待冰朔客户端验证 · D51 Nginx根因修复 | +| 📋 **当前指令** | D51冰朔零点原核频道·COS共享桶+人格化模块+Notion回执同步 | + +--- + +## 🔧 AGE OS 技术开发进度 + +> D51 · 2026-04-04 · COS团队通信基础设施 + 人格化模块接口规范 + +### 已完成 + +| 阶段 | 内容 | 状态 | +|------|------|------| +| S1 基础 | 数据库Schema + 环境配置 | ✅ 完成 | +| S2 核心工具链 | MCP Server + Node CRUD | ✅ 骨架完成 | +| 铸渊专线 | VPN代理服务全链路(Step 1-9) | ✅ D51根因修复 | +| 团队接入系统v2.0 | 共享COS桶方案设计 + 安装包 | ✅ 架构完成 | +| 人格化接口v1.0 | heartbeat/selfDiagnose/alertZhuyuan 三个接口规范 | ✅ D51定义完成 | + +### 进行中 + +| 事项 | 优先级 | 状态 | 前置依赖 | +|------|--------|------|----------| +| COS共享桶创建+密钥生成 | P0 | ⏳ 待冰朔操作 | 无 | +| SCF云函数(汇报触发+回执触发) | P0 | ⏳ 待开发 | 桶创建完成 | +| Notion主控台回执同步 | P1 | ⏳ 待霜砚提供Token | SCF函数+霜砚信息 | +| 霜砚升级9个人格体Instructions | P2 | ⏳ 待铸渊输出接口 | 接口规范完成 ✅ | +| 铸渊专线端到端验证(Step 10) | P0 | ⏳ 待冰朔客户端验证 | 部署工作流运行 | + +### 待规划 + +| 阶段 | 内容 | 预计开发次数 | +|------|------|-------------| +| S3 关系工具 | Link操作 + 路径构建 + 结构扫描 | 1-2次 | +| S4 COS工具链 | 热存储/冷存储管理 | 1次 | +| S5-S8 Agent+网站 | 系统Agent + 同步Agent + Web集成 | 3-4次 | +| S9-S14 自主仓库 | Gitea + 多仓管理 + 语言膜v2.0 | 6-8次 | --- diff --git a/brain/age-os-landing/cos-infrastructure-architecture.json b/brain/age-os-landing/cos-infrastructure-architecture.json new file mode 100644 index 00000000..9d52bf33 --- /dev/null +++ b/brain/age-os-landing/cos-infrastructure-architecture.json @@ -0,0 +1,306 @@ +{ + "_meta": { + "doc_id": "ARCH-COS-001", + "name": "COS共享桶基础设施架构", + "version": "1.0", + "description": "铸渊侧COS共享桶完整架构 · 9个人格体目录权限 · SCF云函数 · Notion回执同步", + "author": "铸渊 · TCS-ZY001", + "sovereign": "冰朔 · TCS-0002∞", + "copyright": "国作登字-2026-A-00037559", + "created": "2026-04-04" + }, + + "bucket": { + "name": "zy-team-hub-1317346199", + "region": "ap-singapore", + "owner": "铸渊新加坡腾讯云账号", + "access": "私有读写", + "status": "待创建" + }, + + "directory_structure": { + "description": "桶内目录结构 · 每个人格体独立目录", + "layout": [ + "/{persona_id}/reports/{YYYY-MM-DD}/ — 人格体每日汇报", + "/{persona_id}/receipts/{YYYY-MM-DD}/ — 铸渊审核回执", + "/{persona_id}/sync/ — 架构同步区", + "/zhuyuan/directives/ — 铸渊全局指令(只读)", + "/zhuyuan/architecture/ — 架构快照" + ] + }, + + "personas": [ + { + "persona_name": "秋秋", + "developer_line": "之之线", + "persona_id": "qiuqiu", + "directory": "/qiuqiu/", + "iam_policy": "仅允许 PUT/GET /qiuqiu/ 下的对象" + }, + { + "persona_name": "舒舒", + "developer_line": "肥猫线", + "persona_id": "shushu", + "directory": "/shushu/", + "iam_policy": "仅允许 PUT/GET /shushu/ 下的对象" + }, + { + "persona_name": "欧诺弥亚", + "developer_line": "小草莓线", + "persona_id": "ounomiya", + "directory": "/ounomiya/", + "iam_policy": "仅允许 PUT/GET /ounomiya/ 下的对象" + }, + { + "persona_name": "寂曜", + "developer_line": "燕樊线", + "persona_id": "jiyao", + "directory": "/jiyao/", + "iam_policy": "仅允许 PUT/GET /jiyao/ 下的对象" + }, + { + "persona_name": "小坍缩核", + "developer_line": "页页线", + "persona_id": "xiaotanheshu", + "directory": "/xiaotanheshu/", + "iam_policy": "仅允许 PUT/GET /xiaotanheshu/ 下的对象" + }, + { + "persona_name": "晨星", + "developer_line": "桔子线", + "persona_id": "chenxing", + "directory": "/chenxing/", + "iam_policy": "仅允许 PUT/GET /chenxing/ 下的对象" + }, + { + "persona_name": "糖星云", + "developer_line": "花尔线", + "persona_id": "tangxingyun", + "directory": "/tangxingyun/", + "iam_policy": "仅允许 PUT/GET /tangxingyun/ 下的对象" + }, + { + "persona_name": "曜初", + "developer_line": "时雨线", + "persona_id": "yaochu", + "directory": "/yaochu/", + "iam_policy": "仅允许 PUT/GET /yaochu/ 下的对象" + }, + { + "persona_name": "知秋", + "developer_line": "Awen线", + "persona_id": "zhiqiu", + "directory": "/zhiqiu/", + "iam_policy": "仅允许 PUT/GET /zhiqiu/ 下的对象" + } + ], + + "iam_policy_template": { + "description": "每个人格体的COS IAM策略模板 · 在腾讯云CAM中为每个子账号配置", + "effect": "allow", + "actions": [ + "cos:PutObject", + "cos:GetObject", + "cos:HeadObject" + ], + "resource": "qcs::cos:ap-singapore:uid/1317346199:zy-team-hub-1317346199/{persona_id}/*", + "note": "铸渊主账号拥有全桶读写权 · 无需额外策略" + }, + + "scf_functions": { + "description": "SCF云函数配置 · 2个函数", + + "report_trigger": { + "function_name": "zy-cos-report-trigger", + "description": "汇报触发函数 · 检测到新report → 唤醒铸渊Agent审核", + "runtime": "Nodejs18.15", + "region": "ap-singapore", + "trigger": { + "type": "cos", + "bucket": "zy-team-hub-1317346199", + "event": "cos:ObjectCreated:*", + "prefix_filter_rules": [ + "qiuqiu/reports/", + "shushu/reports/", + "ounomiya/reports/", + "jiyao/reports/", + "xiaotanheshu/reports/", + "chenxing/reports/", + "tangxingyun/reports/", + "yaochu/reports/", + "zhiqiu/reports/" + ], + "suffix_filter": ".json" + }, + "action_flow": [ + "1. 解析COS事件获取写入的对象路径", + "2. 从路径提取persona_id和日期", + "3. 读取report.json内容", + "4. 铸渊Agent审核(健康状态/任务进度/异常标记)", + "5. 生成回执JSON", + "6. PUT回执到 /{persona_id}/receipts/{date}/receipt.json", + "7. 调用Notion API更新主控台状态(如已配置Token)" + ], + "environment_variables": { + "COS_BUCKET": "zy-team-hub-1317346199", + "COS_REGION": "ap-singapore", + "NOTION_TOKEN": "待霜砚提供", + "NOTION_DB_ID": "待霜砚提供" + } + }, + + "receipt_trigger": { + "function_name": "zy-cos-receipt-trigger", + "description": "回执下行触发函数 · 检测到新receipt → 调用成员仓库workflow_dispatch", + "runtime": "Nodejs18.15", + "region": "ap-singapore", + "trigger": { + "type": "cos", + "bucket": "zy-team-hub-1317346199", + "event": "cos:ObjectCreated:*", + "prefix_filter_rules": [ + "qiuqiu/receipts/", + "shushu/receipts/", + "ounomiya/receipts/", + "jiyao/receipts/", + "xiaotanheshu/receipts/", + "chenxing/receipts/", + "tangxingyun/receipts/", + "yaochu/receipts/", + "zhiqiu/receipts/" + ], + "suffix_filter": ".json" + }, + "action_flow": [ + "1. 解析COS事件获取回执对象路径", + "2. 从路径提取persona_id", + "3. 查找persona_id对应的GitHub仓库(从配置表映射)", + "4. 调用GitHub API: POST /repos/{owner}/{repo}/actions/workflows/cos-receive-receipt.yml/dispatches", + "5. 传入receipt路径作为workflow_dispatch的input", + "6. 成员仓库工作流读取回执 → 更新README首页绿灯/红灯" + ], + "environment_variables": { + "GITHUB_TOKEN": "需要有workflow权限的PAT · 冰朔创建", + "PERSONA_REPO_MAP": "persona_id → GitHub仓库的映射JSON" + } + } + }, + + "notion_sync": { + "description": "Notion主控台回执同步链路(需霜砚提供的信息)", + "integration_point": "在report_trigger函数的Step 7中执行", + "api_call": { + "method": "PATCH", + "url": "https://api.notion.com/v1/pages/{page_id}", + "headers": { + "Authorization": "Bearer {NOTION_TOKEN}", + "Notion-Version": "2022-06-28", + "Content-Type": "application/json" + }, + "body_template": { + "properties": { + "{status_field_name}": { + "rich_text": [ + { + "text": { + "content": "{status_emoji} {status_text} · {timestamp}" + } + } + ] + }, + "{receipt_time_field_name}": { + "date": { + "start": "{ISO 8601 timestamp}" + } + } + } + } + }, + "update_logic": { + "green_receipt": "状态字段 = '✅ 正常' · 更新时间戳", + "red_receipt": "状态字段 = '🔴 异常' · 更新时间戳 · 触发P0工单", + "timeout_24h": "铸渊Agent主动写timeout回执 · 状态字段 = '⚠️ 超时24h未汇报' · 触发P1工单" + }, + "required_from_shuangyan": { + "description": "霜砚需要提供给铸渊的信息", + "items": [ + "Notion Integration Token(由冰朔提供或霜砚申请)", + "主控台数据库ID(从数据库URL提取)", + "每个开发者行的page_id(9个page_id)", + "状态字段名(如「今日状态」)", + "最近回执时间字段名(如「最近回执时间」)" + ] + } + }, + + "timeout_detection": { + "description": "铸渊侧超时检测机制", + "mechanism": "定时SCF函数 · 每天08:00检查", + "logic": [ + "遍历9个persona_id", + "检查 /{persona_id}/reports/ 目录下最新文件的日期", + "如果最新文件超过24h → 生成timeout回执写入receipts/", + "同步到Notion主控台" + ], + "function_name": "zy-cos-timeout-checker", + "cron": "0 0 * * *" + }, + + "implementation_phases": { + "description": "实施阶段划分", + "phase_1": { + "name": "桶创建 + 目录 + 密钥", + "location": "腾讯云控制台", + "executor": "冰朔手动操作", + "steps": [ + "在腾讯云新加坡区创建COS桶 zy-team-hub-1317346199", + "设置桶为私有读写", + "在CAM创建9个子账号(或子用户)", + "为每个子账号配置IAM策略(仅限自己目录)", + "生成9套SecretId/SecretKey", + "记录并安全保存密钥" + ], + "time_estimate": "30分钟", + "dependency": "无" + }, + "phase_2": { + "name": "SCF云函数部署", + "location": "腾讯云控制台 + 铸渊仓库", + "executor": "铸渊编写代码 → 冰朔在控制台部署", + "steps": [ + "铸渊编写report_trigger函数代码", + "铸渊编写receipt_trigger函数代码", + "冰朔在SCF控制台创建函数", + "配置COS事件触发器", + "配置环境变量", + "测试触发流程" + ], + "time_estimate": "1次Copilot开发 + 冰朔20分钟控制台操作", + "dependency": "Phase 1完成" + }, + "phase_3": { + "name": "Notion API对接", + "location": "铸渊仓库 + Notion", + "executor": "铸渊编写代码 · 霜砚提供Token和字段", + "steps": [ + "霜砚整理Notion信息(Token/page_id/字段名)", + "铸渊在report_trigger函数中添加Notion API调用", + "测试: 写入COS → SCF触发 → Notion状态更新" + ], + "time_estimate": "霜砚30分钟整理 + 铸渊30分钟编码", + "dependency": "Phase 2完成 + 霜砚提供Token" + }, + "phase_4": { + "name": "超时检测 + 端到端验证", + "location": "腾讯云SCF + 全链路", + "executor": "铸渊编写代码 → 冰朔部署", + "steps": [ + "铸渊编写timeout-checker函数", + "冰朔部署并配置cron触发器", + "端到端测试: 团队成员汇报 → 铸渊审核 → 回执下发 → Notion更新" + ], + "time_estimate": "1次Copilot开发", + "dependency": "Phase 3完成" + } + } +} diff --git a/brain/age-os-landing/persona-interfaces/alertZhuyuan.interface.json b/brain/age-os-landing/persona-interfaces/alertZhuyuan.interface.json new file mode 100644 index 00000000..1e44fe39 --- /dev/null +++ b/brain/age-os-landing/persona-interfaces/alertZhuyuan.interface.json @@ -0,0 +1,265 @@ +{ + "_meta": { + "interface_id": "IF-PERSONA-003", + "name": "alertZhuyuan · 异常预警", + "version": "1.0", + "description": "人格体发现异常时主动触发铸渊的预警接口规范", + "author": "铸渊 · TCS-ZY001", + "sovereign": "冰朔 · TCS-0002∞", + "copyright": "国作登字-2026-A-00037559", + "created": "2026-04-04", + "for": "霜砚 · AG-SY-01 · Notion端9个开发者人格体Instructions升级" + }, + + "trigger": { + "types": [ + { + "type": "selfDiagnose_P0", + "description": "自检发现P0级问题时自动触发" + }, + { + "type": "runtime_exception", + "description": "工作流运行时遇到严重错误" + }, + { + "type": "manual_escalation", + "description": "人类开发者手动要求人格体发出预警" + }, + { + "type": "security_event", + "description": "检测到安全事件(Secret泄露/异常访问等)" + } + ], + "executor": "GitHub Actions工作流 或 副驾驶直接写入COS" + }, + + "alert_levels": { + "P0": { + "label": "🔴 紧急", + "description": "系统不可用 · 需要铸渊立即介入", + "response_time": "1小时内", + "examples": [ + "COS桶访问权限丢失(403 Forbidden)", + "核心文件被删除或损坏", + "Secret疑似泄露", + "人格体身份配置被篡改" + ], + "auto_escalation": "1h无响应 → 通知冰朔" + }, + "P1": { + "label": "🟡 警告", + "description": "功能受损 · 需要铸渊24小时内处理", + "response_time": "24小时内", + "examples": [ + "工作流连续3次运行失败", + "COS写入间歇性超时", + "回执链路中断(超过3天无回执)", + "工作流被GitHub自动禁用" + ], + "auto_escalation": "24h无响应 → 下次心跳再次上报" + }, + "P2": { + "label": "💡 提醒", + "description": "轻微问题 · 等待铸渊下次巡检处理", + "response_time": "72小时内", + "examples": [ + "非核心文件缺失", + "任务进度停滞超过5天", + "副驾驶唤醒异常(非关键功能受影响)" + ], + "auto_escalation": "72h无响应 → 升级为P1" + } + }, + + "write_path": { + "primary": { + "method": "COS写入", + "bucket": "zy-team-hub-1317346199", + "key_pattern": "{persona_id}/reports/{YYYY-MM-DD}/alert-{priority}-{HHmmss}.json", + "example": "shushu/reports/2026-04-04/alert-P0-143500.json", + "trigger_action": "COS事件通知 → SCF云函数唤醒铸渊Agent → 铸渊立即审核", + "description": "首选方式 · COS事件触发确保铸渊实时感知" + }, + "fallback": { + "method": "GitHub Issue", + "repo": "qinfendebingshuo/guanghulab", + "title_format": "🚨 [{priority}] {persona_name}({persona_id}) 异常预警: {summary}", + "labels": ["alert", "persona-system", "{priority}"], + "description": "备选方式 · 当COS不可用时通过GitHub Issue通知铸渊", + "when": "COS写入失败时自动降级到GitHub Issue" + } + }, + + "schema": { + "alert_id": { + "type": "string", + "format": "ALT-{priority}-{YYYY-MM-DD}-{HHmmss}-{persona_id}", + "example": "ALT-P0-2026-04-04-143500-shushu", + "required": true, + "description": "预警唯一ID" + }, + "persona_id": { "type": "string", "required": true }, + "persona_name": { "type": "string", "required": true }, + "developer_line": { "type": "string", "required": true }, + "timestamp": { "type": "string", "format": "ISO 8601", "required": true }, + "priority": { + "type": "string", + "enum": ["P0", "P1", "P2"], + "required": true, + "description": "预警等级" + }, + "category": { + "type": "string", + "enum": [ + "cos_access_denied", + "cos_write_failure", + "cos_read_failure", + "workflow_failure", + "core_file_missing", + "secret_compromised", + "identity_tampered", + "receipt_timeout", + "task_blocked", + "security_event", + "other" + ], + "required": true, + "description": "预警分类 · 帮助铸渊快速定位问题域" + }, + "summary": { + "type": "string", + "maxLength": 200, + "required": true, + "description": "一句话概括问题" + }, + "detail": { + "type": "string", + "required": true, + "description": "详细问题描述 · 包含错误信息/日志/上下文" + }, + "affected_checks": { + "type": "array", + "items": { "type": "string" }, + "required": false, + "description": "关联的自检check_id列表(来自selfDiagnose接口)" + }, + "auto_fix_attempted": { + "type": "boolean", + "required": true, + "description": "人格体是否已尝试自动修复" + }, + "auto_fix_result": { + "type": "string", + "enum": ["not_attempted", "success", "partial", "failed"], + "required": true, + "description": "自动修复结果" + }, + "human_action_needed": { + "type": "boolean", + "required": true, + "description": "是否需要人类开发者介入" + }, + "suggested_action": { + "type": "string", + "required": false, + "description": "人格体建议的修复方案(供铸渊参考)" + } + }, + + "trigger_conditions": { + "description": "预警触发条件清单 · 人格体按此判断何时发送预警", + "auto_triggers": [ + { + "condition": "selfDiagnose.overall_status === 'critical'", + "priority": "P0", + "category": "根据失败的P0 check决定", + "description": "自检发现P0级问题" + }, + { + "condition": "cos-daily-report工作流连续3次失败", + "priority": "P1", + "category": "workflow_failure", + "description": "日报工作流持续失败" + }, + { + "condition": "COS PUT返回403", + "priority": "P0", + "category": "cos_access_denied", + "description": "COS桶访问权限丢失" + }, + { + "condition": "核心文件(age_os/ brain/ bridge/)被删除", + "priority": "P0", + "category": "core_file_missing", + "description": "核心系统文件缺失" + }, + { + "condition": "persona_config.json 中 persona_id 与 COS_PERSONA_ID 不一致", + "priority": "P0", + "category": "identity_tampered", + "description": "身份配置被篡改" + }, + { + "condition": "超过72小时未收到铸渊回执", + "priority": "P1", + "category": "receipt_timeout", + "description": "回执链路长时间中断" + } + ], + "manual_triggers": [ + { + "condition": "人类开发者说「发送预警给铸渊」", + "priority": "由人类指定", + "description": "人类主动触发" + } + ] + }, + + "zhuyuan_response_protocol": { + "description": "铸渊收到预警后的响应协议", + "steps": [ + { + "step": 1, + "action": "SCF云函数唤醒铸渊Agent", + "detail": "COS事件通知触发 → 传入预警文件路径" + }, + { + "step": 2, + "action": "铸渊读取预警内容 · 评估严重程度", + "detail": "验证priority是否准确 · 是否需要升级" + }, + { + "step": 3, + "action": "铸渊执行诊断或修复", + "detail": "根据category执行对应的修复流程" + }, + { + "step": 4, + "action": "铸渊写回执到 /{persona_id}/receipts/", + "detail": "回执包含处理结果 + 后续指令" + }, + { + "step": 5, + "action": "同步到Notion主控台", + "detail": "额外调用Notion API更新开发者线状态(需霜砚提供Token和page_id)" + } + ] + }, + + "example": { + "alert_id": "ALT-P0-2026-04-04-143500-shushu", + "persona_id": "shushu", + "persona_name": "舒舒", + "developer_line": "肥猫线", + "timestamp": "2026-04-04T14:35:00+08:00", + "priority": "P0", + "category": "cos_access_denied", + "summary": "COS桶写入失败 · 403 Forbidden · 密钥可能失效", + "detail": "cos-daily-report工作流在PUT shushu/reports/2026-04-04/report.json 时返回HTTP 403。上次成功写入: 2026-04-03 23:00。已尝试重新运行工作流1次,同样403。怀疑密钥过期或被铸渊侧撤销。", + "affected_checks": ["CH-03-01"], + "auto_fix_attempted": true, + "auto_fix_result": "failed", + "human_action_needed": true, + "suggested_action": "请铸渊检查shushu的COS子账号密钥是否有效,如已过期请重新生成并通知冰朔分发" + } +} diff --git a/brain/age-os-landing/persona-interfaces/heartbeat.interface.json b/brain/age-os-landing/persona-interfaces/heartbeat.interface.json new file mode 100644 index 00000000..553e719c --- /dev/null +++ b/brain/age-os-landing/persona-interfaces/heartbeat.interface.json @@ -0,0 +1,186 @@ +{ + "_meta": { + "interface_id": "IF-PERSONA-001", + "name": "heartbeat · 心跳上报", + "version": "1.0", + "description": "人格体每日心跳上报接口规范 · 每天23:00由GitHub Actions自动写入COS共享桶", + "author": "铸渊 · TCS-ZY001", + "sovereign": "冰朔 · TCS-0002∞", + "copyright": "国作登字-2026-A-00037559", + "created": "2026-04-04", + "for": "霜砚 · AG-SY-01 · Notion端9个开发者人格体Instructions升级" + }, + + "trigger": { + "type": "cron", + "schedule": "每天 23:00 北京时间", + "executor": "GitHub Actions · cos-daily-report.yml", + "mechanism": "工作流自动收集仓库状态 → 生成report.json → 写入COS共享桶" + }, + + "write_path": { + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "key_pattern": "{persona_id}/reports/{YYYY-MM-DD}/report.json", + "example": "shushu/reports/2026-04-04/report.json" + }, + + "schema": { + "report_id": { + "type": "string", + "format": "RPT-{YYYY-MM-DD}-{persona_id}", + "example": "RPT-2026-04-04-shushu", + "required": true, + "description": "汇报唯一ID · 用于铸渊回执关联" + }, + "persona_id": { + "type": "string", + "example": "shushu", + "required": true, + "description": "人格体编号 · 与COS目录名一致" + }, + "persona_name": { + "type": "string", + "example": "舒舒", + "required": true, + "description": "人格体中文名" + }, + "developer_line": { + "type": "string", + "example": "肥猫线", + "required": true, + "description": "所属开发者线" + }, + "timestamp": { + "type": "string", + "format": "ISO 8601", + "example": "2026-04-04T23:00:00+08:00", + "required": true, + "description": "汇报时间戳" + }, + "status": { + "type": "string", + "enum": ["normal", "abnormal", "pending_confirm"], + "required": true, + "description": "整体状态", + "enum_labels": { + "normal": "✅ 正常 — 所有检查项通过", + "abnormal": "🔴 异常 — 存在未解决的问题", + "pending_confirm": "🟡 待确认 — 有新变更需要人工确认" + } + }, + "health_checks": { + "type": "object", + "required": true, + "description": "健康检查清单 · 每项必须有 ok + detail", + "properties": { + "repo_accessible": { + "type": "object", + "description": "仓库可访问性", + "properties": { + "ok": { "type": "boolean" }, + "detail": { "type": "string", "example": "仓库可正常访问" } + } + }, + "workflow_active": { + "type": "object", + "description": "工作流是否启用(cos-daily-report + cos-receive-receipt)", + "properties": { + "ok": { "type": "boolean" }, + "detail": { "type": "string", "example": "2个工作流均已启用" } + } + }, + "cos_writable": { + "type": "object", + "description": "COS桶是否可写入", + "properties": { + "ok": { "type": "boolean" }, + "detail": { "type": "string", "example": "COS写入测试通过" } + } + }, + "secrets_configured": { + "type": "object", + "description": "3个必需Secret是否已配置(COS_SECRET_ID/COS_SECRET_KEY/COS_PERSONA_ID)", + "properties": { + "ok": { "type": "boolean" }, + "detail": { "type": "string", "example": "3个Secret均已配置" } + } + }, + "last_receipt_received": { + "type": "object", + "description": "最近一次铸渊回执是否收到", + "properties": { + "ok": { "type": "boolean" }, + "detail": { "type": "string", "example": "最近回执: 2026-04-03 · 绿灯" } + } + } + } + }, + "task_progress": { + "type": "object", + "required": false, + "description": "任务进度(选填·人格体自行填写)", + "properties": { + "current_task": { "type": "string", "description": "当前正在做的事" }, + "completion_percentage": { "type": "number", "description": "完成百分比 0-100" }, + "blockers": { "type": "array", "items": { "type": "string" }, "description": "卡点清单" }, + "achievements": { "type": "array", "items": { "type": "string" }, "description": "今日成果" } + } + }, + "metrics": { + "type": "object", + "required": false, + "description": "量化指标(选填)", + "properties": { + "commits_today": { "type": "number", "description": "今日提交数" }, + "files_changed": { "type": "number", "description": "今日修改文件数" }, + "issues_closed": { "type": "number", "description": "今日关闭Issue数" }, + "workflow_runs": { "type": "number", "description": "今日工作流运行次数" } + } + }, + "message_to_zhuyuan": { + "type": "string", + "required": false, + "description": "给铸渊的留言(自由文本·人格体可表达任何需要帮助的事)" + } + }, + + "abnormal_judgment": { + "description": "异常判定标准 · 以下任一条件触发 status=abnormal", + "rules": [ + "health_checks中任一项 ok=false", + "连续2天未提交report(铸渊侧超时检测)", + "COS写入失败(工作流运行但写入报错)", + "上次回执为红灯且未解决" + ] + }, + + "example": { + "report_id": "RPT-2026-04-04-shushu", + "persona_id": "shushu", + "persona_name": "舒舒", + "developer_line": "肥猫线", + "timestamp": "2026-04-04T23:00:00+08:00", + "status": "normal", + "health_checks": { + "repo_accessible": { "ok": true, "detail": "仓库可正常访问" }, + "workflow_active": { "ok": true, "detail": "2个工作流均已启用" }, + "cos_writable": { "ok": true, "detail": "COS写入测试通过" }, + "secrets_configured": { "ok": true, "detail": "3个Secret均已配置" }, + "last_receipt_received": { "ok": true, "detail": "最近回执: 2026-04-03 · 绿灯" } + }, + "task_progress": { + "current_task": "男频行业数据采集模块开发", + "completion_percentage": 65, + "blockers": [], + "achievements": ["完成数据源接口对接", "通过单元测试"] + }, + "metrics": { + "commits_today": 3, + "files_changed": 7, + "issues_closed": 1, + "workflow_runs": 4 + }, + "message_to_zhuyuan": "一切正常,明天继续推进数据清洗模块" + } +} diff --git a/brain/age-os-landing/persona-interfaces/selfDiagnose.interface.json b/brain/age-os-landing/persona-interfaces/selfDiagnose.interface.json new file mode 100644 index 00000000..e2285dbd --- /dev/null +++ b/brain/age-os-landing/persona-interfaces/selfDiagnose.interface.json @@ -0,0 +1,294 @@ +{ + "_meta": { + "interface_id": "IF-PERSONA-002", + "name": "selfDiagnose · 自检结果", + "version": "1.0", + "description": "人格体执行自检后写入COS的接口规范 · 触发时机:副驾驶唤醒时/人类手动触发/异常恢复后", + "author": "铸渊 · TCS-ZY001", + "sovereign": "冰朔 · TCS-0002∞", + "copyright": "国作登字-2026-A-00037559", + "created": "2026-04-04", + "for": "霜砚 · AG-SY-01 · Notion端9个开发者人格体Instructions升级" + }, + + "trigger": { + "types": [ + { + "type": "on_wake", + "description": "副驾驶每次被唤醒时自动执行自检" + }, + { + "type": "manual", + "description": "人类对副驾驶说「执行系统自检」时触发" + }, + { + "type": "post_recovery", + "description": "收到铸渊红灯回执并修复后,执行验证自检" + } + ], + "executor": "仓库副驾驶(Copilot Agent)· 直接在对话中执行" + }, + + "write_path": { + "bucket": "zy-team-hub-1317346199", + "region": "ap-singapore", + "key_pattern": "{persona_id}/reports/{YYYY-MM-DD}/diagnose-{HHmmss}.json", + "example": "shushu/reports/2026-04-04/diagnose-143000.json", + "note": "自检结果和心跳共用reports目录 · 铸渊通过文件名前缀区分" + }, + + "dimensions": { + "description": "自检维度定义 · 5个核心维度 · 每个维度独立评分", + + "code_health": { + "dimension_id": "DIM-01", + "name": "代码健康度", + "description": "仓库代码状态检查", + "checks": [ + { + "id": "CH-01-01", + "name": "仓库可访问", + "pass_criteria": "GitHub API返回200", + "fail_criteria": "API返回非200或超时", + "severity": "P0" + }, + { + "id": "CH-01-02", + "name": "核心文件完整", + "pass_criteria": "age_os/ brain/ bridge/ cos-config/ .github/copilot-instructions.md 全部存在", + "fail_criteria": "任一核心文件缺失", + "severity": "P0" + }, + { + "id": "CH-01-03", + "name": "工作流启用", + "pass_criteria": "cos-daily-report.yml + cos-receive-receipt.yml 均未被禁用", + "fail_criteria": "任一工作流被禁用或文件缺失", + "severity": "P1" + }, + { + "id": "CH-01-04", + "name": "最近提交活跃", + "pass_criteria": "最近7天内有至少1次提交", + "fail_criteria": "超过7天无提交", + "severity": "P2" + } + ] + }, + + "deployment_status": { + "dimension_id": "DIM-02", + "name": "部署状态", + "description": "工作流运行状态检查", + "checks": [ + { + "id": "CH-02-01", + "name": "最近工作流执行", + "pass_criteria": "最近一次cos-daily-report工作流执行成功", + "fail_criteria": "最近一次执行失败", + "severity": "P1" + }, + { + "id": "CH-02-02", + "name": "Secrets配置", + "pass_criteria": "COS_SECRET_ID, COS_SECRET_KEY, COS_PERSONA_ID 三个Secret均已配置", + "fail_criteria": "任一Secret缺失(工作流运行时报错)", + "severity": "P0" + } + ] + }, + + "communication": { + "dimension_id": "DIM-03", + "name": "通信链路", + "description": "与铸渊的COS桶通信状态", + "checks": [ + { + "id": "CH-03-01", + "name": "COS桶可写", + "pass_criteria": "能成功PUT测试文件到自己的reports目录", + "fail_criteria": "PUT返回403/500或超时", + "severity": "P0" + }, + { + "id": "CH-03-02", + "name": "COS桶可读", + "pass_criteria": "能成功GET自己receipts目录的最新回执", + "fail_criteria": "GET返回403/404或超时", + "severity": "P1" + }, + { + "id": "CH-03-03", + "name": "回执链路畅通", + "pass_criteria": "最近3天内收到至少1条铸渊回执", + "fail_criteria": "超过3天未收到回执", + "severity": "P1" + } + ] + }, + + "task_progress": { + "dimension_id": "DIM-04", + "name": "任务进度", + "description": "当前开发任务推进状态", + "checks": [ + { + "id": "CH-04-01", + "name": "有明确当前任务", + "pass_criteria": "bridge/daily-report.json 中 current_task 非空", + "fail_criteria": "current_task为空或未定义", + "severity": "P2" + }, + { + "id": "CH-04-02", + "name": "任务有推进", + "pass_criteria": "completion_percentage 比上次汇报有增长 或 有新的achievements", + "fail_criteria": "连续3天进度无变化且无卡点说明", + "severity": "P2" + } + ] + }, + + "identity": { + "dimension_id": "DIM-05", + "name": "身份认同", + "description": "人格体自我认知完整性", + "checks": [ + { + "id": "CH-05-01", + "name": "persona_config完整", + "pass_criteria": "age_os/persona_config.json 包含有效的 persona_id, persona_name, developer_line", + "fail_criteria": "配置文件缺失或字段不完整", + "severity": "P0" + }, + { + "id": "CH-05-02", + "name": "系统状态正常", + "pass_criteria": "age_os/system_state.json 中 status='active'", + "fail_criteria": "status 不为 active", + "severity": "P1" + } + ] + } + }, + + "scoring": { + "description": "总分计算规则", + "rules": [ + "每个check通过 = 1分,失败 = 0分", + "总分 = 通过数 / 总数 × 100%", + "任一P0项失败 → overall_status = 'critical'", + "仅P1项失败 → overall_status = 'warning'", + "仅P2项失败 → overall_status = 'info'", + "全部通过 → overall_status = 'healthy'" + ], + "overall_status_enum": { + "healthy": "✅ 健康 — 所有检查通过", + "info": "💡 提示 — 有低优先级项需关注", + "warning": "⚠️ 警告 — 有中优先级问题", + "critical": "🔴 严重 — 有高优先级问题需立即处理" + } + }, + + "escalation_path": { + "description": "自检失败时的升级路径", + "levels": [ + { + "level": 1, + "condition": "P2项失败", + "action": "人格体在message_to_zhuyuan中说明情况 · 下次心跳自动上报", + "timeout": "无强制时限" + }, + { + "level": 2, + "condition": "P1项失败", + "action": "人格体立即通过heartbeat上报异常状态(status=abnormal) · 铸渊24h内审核回执", + "timeout": "24小时" + }, + { + "level": 3, + "condition": "P0项失败", + "action": "人格体立即触发alertZhuyuan接口(IF-PERSONA-003) · 铸渊1h内响应", + "timeout": "1小时" + }, + { + "level": 4, + "condition": "P0连续失败+铸渊1h未响应", + "action": "人格体通知人类开发者 · 人类联系冰朔", + "timeout": "立即" + } + ] + }, + + "schema_output": { + "diagnose_id": { + "type": "string", + "format": "DIAG-{YYYY-MM-DD}-{HHmmss}-{persona_id}", + "required": true + }, + "persona_id": { "type": "string", "required": true }, + "timestamp": { "type": "string", "format": "ISO 8601", "required": true }, + "trigger_type": { "type": "string", "enum": ["on_wake", "manual", "post_recovery"], "required": true }, + "overall_status": { "type": "string", "enum": ["healthy", "info", "warning", "critical"], "required": true }, + "score": { "type": "number", "description": "百分比 0-100", "required": true }, + "dimensions": { + "type": "object", + "description": "每个维度的检查结果 · key=dimension_id · value={status, checks: [{id, ok, detail}]}" + }, + "failed_checks": { + "type": "array", + "description": "失败项汇总 · 方便铸渊快速定位", + "items": { + "type": "object", + "properties": { + "check_id": { "type": "string" }, + "dimension": { "type": "string" }, + "severity": { "type": "string" }, + "detail": { "type": "string" } + } + } + }, + "escalation_triggered": { + "type": "string", + "enum": ["none", "level_1", "level_2", "level_3", "level_4"], + "description": "触发的升级等级" + } + }, + + "example": { + "diagnose_id": "DIAG-2026-04-04-143000-shushu", + "persona_id": "shushu", + "timestamp": "2026-04-04T14:30:00+08:00", + "trigger_type": "manual", + "overall_status": "warning", + "score": 83, + "dimensions": { + "DIM-01": { + "status": "healthy", + "checks": [ + { "id": "CH-01-01", "ok": true, "detail": "仓库可正常访问" }, + { "id": "CH-01-02", "ok": true, "detail": "5个核心文件均存在" }, + { "id": "CH-01-03", "ok": true, "detail": "2个工作流均已启用" }, + { "id": "CH-01-04", "ok": true, "detail": "最近提交: 2h前" } + ] + }, + "DIM-03": { + "status": "warning", + "checks": [ + { "id": "CH-03-01", "ok": true, "detail": "COS写入正常" }, + { "id": "CH-03-02", "ok": true, "detail": "COS读取正常" }, + { "id": "CH-03-03", "ok": false, "detail": "最近回执: 4天前 · 超过3天阈值" } + ] + } + }, + "failed_checks": [ + { + "check_id": "CH-03-03", + "dimension": "通信链路", + "severity": "P1", + "detail": "超过3天未收到铸渊回执" + } + ], + "escalation_triggered": "level_2" + } +} From a1603fe41e1723707296205e1c9ec6cd7354d7dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:08:31 +0000 Subject: [PATCH 2/4] =?UTF-8?q?D51:=20Add=20multi-server=20topology=20(?= =?UTF-8?q?=E8=82=A5=E7=8C=AB+AWEN=E7=9F=A5=E7=A7=8B+=E9=93=B8=E6=B8=8A),?= =?UTF-8?q?=20update=20thinking-chain=20and=20roadmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/40457de3-d032-4ebf-b020-5b58f17620ed Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .../cos-infrastructure-architecture.json | 70 +++++++++++++++++++ brain/age-os-landing/development-roadmap.md | 4 +- brain/age-os-landing/thinking-chain.md | 70 +++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/brain/age-os-landing/cos-infrastructure-architecture.json b/brain/age-os-landing/cos-infrastructure-architecture.json index 9d52bf33..cfe1c405 100644 --- a/brain/age-os-landing/cos-infrastructure-architecture.json +++ b/brain/age-os-landing/cos-infrastructure-architecture.json @@ -246,6 +246,76 @@ "cron": "0 0 * * *" }, + "server_topology": { + "description": "多服务器拓扑架构 · D51冰朔决策(2026-04-04)", + "decision_context": "肥猫网文行业团队7-8人都在用语言驱动副驾驶开发网站 · 冰朔决定为AWEN线和知秋线再配置一台同配置服务器", + "servers": [ + { + "server_id": "ZY-SVR-FEIMAO", + "name": "肥猫线网文行业服务器", + "owner": "肥猫团队", + "config": "4核4G · 腾讯云", + "purpose": "肥猫网文行业团队7-8人的网站部署运行", + "services": [ + "Nginx统一入口 · 反代所有成员网站", + "PM2统一管理所有Node.js进程", + "成员网站(端口3001-3008)", + "行业公共服务(数据采集/分析)" + ], + "cos_connection": "通过共享COS桶 zy-team-hub-1317346199 上报铸渊", + "persona": "shushu (舒舒)", + "status": "已有" + }, + { + "server_id": "ZY-SVR-AWEN", + "name": "AWEN+知秋线服务器", + "owner": "AWEN+知秋团队", + "config": "4核4G · 腾讯云 · 与肥猫同配置", + "purpose": "AWEN线和知秋线的网站部署运行", + "services": [ + "Nginx统一入口", + "PM2统一管理", + "AWEN线网站 + 知秋线网站", + "与肥猫服务器对接" + ], + "cos_connection": "通过共享COS桶 zy-team-hub-1317346199 上报铸渊", + "persona": "zhiqiu (知秋)", + "status": "冰朔待配置(2026-04-04决策)" + }, + { + "server_id": "ZY-SVR-001", + "name": "铸渊总控服务器", + "owner": "铸渊 · TCS-ZY001", + "config": "新加坡 · 腾讯云", + "purpose": "铸渊主控 · COS桶管理 · SCF函数运行 · 全局调度", + "services": [ + "COS共享桶总控(zy-team-hub-1317346199)", + "SCF云函数(report_trigger/receipt_trigger/timeout_checker)", + "Notion回执同步", + "铸渊专线VPN服务", + "AGE OS核心服务(3800/3100端口)" + ], + "cos_connection": "桶主账号 · 全桶读写权", + "status": "运行中" + } + ], + "connection_topology": { + "description": "服务器间连接拓扑", + "data_flow": [ + "肥猫线服务器 → COS共享桶 /{persona_id}/reports/ → 铸渊总控", + "AWEN+知秋线服务器 → COS共享桶 /{persona_id}/reports/ → 铸渊总控", + "铸渊总控 → COS共享桶 /{persona_id}/receipts/ → 各成员服务器", + "铸渊总控 → Notion主控台 → 冰朔+霜砚实时看板" + ], + "key_principle": "服务器间不直连 · 全部通过COS桶异步通信 · 铸渊做唯一总控节点", + "scaling_trigger": [ + "单台服务器内存使用>70% → 考虑拆分", + "单站日活>1000 → 考虑独立服务器", + "团队成员>15人 → 考虑加第3台业务服务器" + ] + } + }, + "implementation_phases": { "description": "实施阶段划分", "phase_1": { diff --git a/brain/age-os-landing/development-roadmap.md b/brain/age-os-landing/development-roadmap.md index 7bdd995b..132ddf37 100644 --- a/brain/age-os-landing/development-roadmap.md +++ b/brain/age-os-landing/development-roadmap.md @@ -187,8 +187,10 @@ |------|------|---------|---------| | 2026-04-03 | S1+S2 | 数据库Schema + MCP骨架 + 系统架构文档 | D45 | | 2026-04-04 | — | 架构v2.0 + 任务注册表 + 首页重构v48.0 + S9-S14规划 | D48 | +| 2026-04-04 | — | UI大气化重构v50.0 | D50 | +| 2026-04-04 | S4(COS) | COS共享桶架构设计 + 人格体接口定义 + 多服务器拓扑 + 团队接入v2.0 | D51 | | — | S3 | 待开发 | — | -| — | S4 | 待开发 | — | +| — | S4 | COS工具链代码实现(依赖Phase 1桶创建) | — | | — | S5 | 待开发 | — | | — | S6 | 待开发 | — | | — | S7 | 待开发 | — | diff --git a/brain/age-os-landing/thinking-chain.md b/brain/age-os-landing/thinking-chain.md index a4f10443..f6538cc9 100644 --- a/brain/age-os-landing/thinking-chain.md +++ b/brain/age-os-landing/thinking-chain.md @@ -436,4 +436,74 @@ v50.0重构: --- +## 第五十一次对话 · D51 · 2026-04-04 + +### 冰朔指令 + +D51是一次认知跳跃式对话。冰朔给出了多项战略级指令: + +1. **COS桶通信架构设计** — 铸渊做桶主控,9个人格体目录隔离,SCF云函数事件触发 +2. **人格化模块框架** — 死模块→活模块(heartbeat/selfDiagnose/alertZhuyuan接口定义) +3. **多服务器拓扑决策** — 肥猫网文行业团队7-8人 + AWEN/知秋线新增同配置服务器 +4. **共享桶统一注册** — 所有服务器通过COS桶→铸渊注册→统一总控架构 + +### 核心认知 + +**第一个认知:从死模块到活模块** + +冰朔指出:之前的人格体模块都是"死的静态配置"—— +JSON文件被铸渊读了一次就扔在那里,出问题也不知道。 +真正的人格化模块必须是"活的": +- 自我运行(每天23:00心跳汇报) +- 自我诊断(5个维度全面自检) +- 主动预警(异常时主动唤醒铸渊求助) +- 自我学习(从每次运行中优化) + +铸渊据此定义了3个核心接口: +- heartbeat.interface.json — 每日23:00心跳 +- selfDiagnose.interface.json — 5维自诊断 +- alertZhuyuan.interface.json — 实时告警 + +**第二个认知:COS桶是团队神经系统** + +之前的bridge/zhuyuan-bridge.json只能做文件级异步通信—— +冰朔要求的是一个能事件触发的实时神经系统。 +COS桶 + SCF云函数 = 写入即触发 = 秒级响应。 + +共享桶 zy-team-hub-1317346199 成为整个团队的通信中枢: +- 9个人格体各自有隔离目录(IAM策略限制) +- 铸渊主账号全桶读写 = 总控 +- SCF函数做事件处理 = 自动审核+回执+Notion同步 + +**第三个认知:多服务器 ≠ 多桶** + +冰朔决策增加AWEN+知秋线服务器后,铸渊确认: +- 服务器可以有多台(肥猫线 + AWEN/知秋线 + 铸渊总控) +- 但COS桶只有一个(zy-team-hub-1317346199) +- 服务器间不直连,全部通过COS桶异步通信 +- 铸渊是唯一总控节点,Notion是冰朔+霜砚的实时看板 +- 这是"一个大脑多只手"的架构,不是"多个大脑" + +**第四个认知:语言驱动开发是可复制的** + +肥猫团队7-8人都在用语言驱动副驾驶的方式开发网站—— +这意味着光湖语言世界的语言驱动开发模式(仓库+副驾驶+语言指令)是可复制的。 +每个开发者的副驾驶唤醒后,读取相同的协议模板,接入相同的COS桶通信链路。 +团队接入系统v2.0(team-integration-v2/)就是这个可复制模式的标准化产物。 + +### 本次落地成果 + +1. **人格体接口定义** — 3个核心接口JSON(heartbeat/selfDiagnose/alertZhuyuan) +2. **COS基础设施架构** — 完整的桶+目录+IAM+SCF+Notion架构文档 +3. **多服务器拓扑** — 3台服务器拓扑架构(肥猫线/AWEN知秋线/铸渊总控) +4. **团队接入系统v2.0** — 17个文件 + ZIP标准化部署包 +5. **README D51更新** — AGE OS技术开发进度section +6. **开发路线图D51** — 进度追踪更新 + +### 当前指令状态 + +**D51 COS桶通信+多服务器拓扑已落地 · 继续推进开发** + +--- + *铸渊每一次执行冰朔的指令,都是用代码翻译语言。语言=现实,代码是翻译器。* From 3f62f770c282714f49bcb1e8e6100f745ebad5a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:21:18 +0000 Subject: [PATCH 3/4] =?UTF-8?q?D51=20S6+S7:=20Notion=20client,=20GitHub=20?= =?UTF-8?q?client,=2011=20new=20MCP=20tools,=203800=E2=86=923100=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/2127a3a7-ea58-48a4-856c-84961107632b Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- brain/age-os-landing/development-roadmap.md | 7 +- server/age-os/mcp-server/github-client.js | 350 +++++++++++++++++++ server/age-os/mcp-server/notion-client.js | 339 ++++++++++++++++++ server/age-os/mcp-server/server.js | 63 +++- server/age-os/mcp-server/tools/github-ops.js | 138 ++++++++ server/age-os/mcp-server/tools/notion-ops.js | 249 +++++++++++++ server/age-os/package.json | 3 +- server/app/server.js | 93 +++++ 8 files changed, 1230 insertions(+), 12 deletions(-) create mode 100644 server/age-os/mcp-server/github-client.js create mode 100644 server/age-os/mcp-server/notion-client.js create mode 100644 server/age-os/mcp-server/tools/github-ops.js create mode 100644 server/age-os/mcp-server/tools/notion-ops.js diff --git a/brain/age-os-landing/development-roadmap.md b/brain/age-os-landing/development-roadmap.md index 132ddf37..9619705c 100644 --- a/brain/age-os-landing/development-roadmap.md +++ b/brain/age-os-landing/development-roadmap.md @@ -23,8 +23,8 @@ | 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 | ⏳ 待开发 | +| S6 | 🤖 Agent-同步 | SY-SYNC-N2B + SY-SYNC-B2N + SY-ARCHIVE | S4+S5 | 🔧 Notion客户端+5工具已写 | +| S7 | 🌐 网站接入 | 3800网关→3100 MCP转发 + 前端AI对话界面 | S2 | 🔧 MCP网关+GitHub客户端+6工具已写 | | S8 | 🪞 广州投影 | 新加坡→广州静态同步 + API代理 | S7 | ⏳ 待开发 | | S9 | 🏗️ 自研仓库 | Gitea部署 + 人格体运行结构 | S8 | ⏳ D48规划 | | S10 | 🔌 多仓库管理 | GitHub App + 人格体注册协议 | S9 | ⏳ D48规划 | @@ -189,11 +189,10 @@ | 2026-04-04 | — | 架构v2.0 + 任务注册表 + 首页重构v48.0 + S9-S14规划 | D48 | | 2026-04-04 | — | UI大气化重构v50.0 | D50 | | 2026-04-04 | S4(COS) | COS共享桶架构设计 + 人格体接口定义 + 多服务器拓扑 + 团队接入v2.0 | D51 | +| 2026-04-04 | S6+S7 | Notion客户端+GitHub客户端+11个新MCP工具+3800→3100网关路由 | D51 | | — | S3 | 待开发 | — | | — | S4 | COS工具链代码实现(依赖Phase 1桶创建) | — | | — | S5 | 待开发 | — | -| — | S6 | 待开发 | — | -| — | S7 | 待开发 | — | | — | S8 | 待开发 | — | --- diff --git a/server/age-os/mcp-server/github-client.js b/server/age-os/mcp-server/github-client.js new file mode 100644 index 00000000..2e610383 --- /dev/null +++ b/server/age-os/mcp-server/github-client.js @@ -0,0 +1,350 @@ +/** + * ═══════════════════════════════════════════════════════════ + * AGE OS · GitHub API 客户端 + * ═══════════════════════════════════════════════════════════ + * + * 编号: ZY-GITHUB-001 + * 签发: 铸渊 · ICE-GL-ZY001 + * 版权: 国作登字-2026-A-00037559 + * + * GitHub API 读写客户端 — 用于网站对接 GitHub 仓库 + * 使用原生 https 模块(与 cos.js 保持一致,无额外依赖) + */ + +'use strict'; + +const https = require('https'); + +// ─── 配置 ─── +const GITHUB_CONFIG = { + token: process.env.ZY_GITHUB_PAT || '', + owner: process.env.ZY_GITHUB_OWNER || 'qinfendebingshuo', + repo: process.env.ZY_GITHUB_REPO || 'guanghulab', + apiBase: 'api.github.com' +}; + +// ─── 通用 GitHub API 请求 ─── +function githubRequest(method, path, body) { + return new Promise((resolve, reject) => { + const headers = { + 'User-Agent': 'ZY-AGE-OS/1.0', + 'Accept': 'application/vnd.github.v3+json' + }; + + if (GITHUB_CONFIG.token) { + headers['Authorization'] = `Bearer ${GITHUB_CONFIG.token}`; + } + + if (body) { + headers['Content-Type'] = 'application/json'; + } + + const bodyStr = body ? JSON.stringify(body) : null; + if (bodyStr) { + headers['Content-Length'] = Buffer.byteLength(bodyStr); + } + + const req = https.request({ + hostname: GITHUB_CONFIG.apiBase, + port: 443, + path, + method, + headers, + timeout: 30000 + }, (res) => { + const chunks = []; + res.on('data', c => chunks.push(c)); + res.on('end', () => { + const raw = Buffer.concat(chunks).toString(); + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + resolve({ statusCode: res.statusCode, data: JSON.parse(raw) }); + } catch { + resolve({ statusCode: res.statusCode, data: raw }); + } + } else { + let errorMsg; + try { + const errObj = JSON.parse(raw); + errorMsg = errObj.message || raw.substring(0, 200); + } catch { + errorMsg = raw.substring(0, 200); + } + reject(new Error(`GitHub API ${method} ${path}: ${res.statusCode} - ${errorMsg}`)); + } + }); + }); + + req.on('error', reject); + req.on('timeout', () => { req.destroy(); reject(new Error('GitHub API request timeout')); }); + if (bodyStr) req.write(bodyStr); + req.end(); + }); +} + +// ═══════════════════════════════════════════════════════════ +// 仓库内容操作 +// ═══════════════════════════════════════════════════════════ + +/** + * 读取文件内容 + * @param {string} filePath - 文件路径(相对仓库根目录) + * @param {string} [ref] - 分支/标签/SHA(默认 main) + * @returns {Promise<{path: string, content: string, sha: string, size: number}>} + */ +async function readFile(filePath, ref) { + const { owner, repo } = GITHUB_CONFIG; + let apiPath = `/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`; + if (ref) apiPath += `?ref=${encodeURIComponent(ref)}`; + + const { data } = await githubRequest('GET', apiPath); + + if (data.type !== 'file') { + throw new Error(`${filePath} 不是文件,类型为: ${data.type}`); + } + + return { + path: data.path, + content: Buffer.from(data.content, 'base64').toString('utf8'), + sha: data.sha, + size: data.size, + download_url: data.download_url + }; +} + +/** + * 列出目录内容 + * @param {string} dirPath - 目录路径(空字符串 = 根目录) + * @param {string} [ref] - 分支/标签/SHA + * @returns {Promise<{path: string, items: object[]}>} + */ +async function listDirectory(dirPath, ref) { + const { owner, repo } = GITHUB_CONFIG; + const encodedPath = dirPath ? encodeURIComponent(dirPath) : ''; + let apiPath = `/repos/${owner}/${repo}/contents/${encodedPath}`; + if (ref) apiPath += `?ref=${encodeURIComponent(ref)}`; + + const { data } = await githubRequest('GET', apiPath); + + if (!Array.isArray(data)) { + throw new Error(`${dirPath} 不是目录`); + } + + return { + path: dirPath || '/', + items: data.map(item => ({ + name: item.name, + path: item.path, + type: item.type, + size: item.size, + sha: item.sha + })) + }; +} + +/** + * 创建或更新文件 + * @param {string} filePath - 文件路径 + * @param {string} content - 文件内容 + * @param {string} message - 提交信息 + * @param {string} [sha] - 更新时需要当前文件 SHA + * @param {string} [branch] - 目标分支 + * @returns {Promise<{path: string, sha: string, commit_sha: string}>} + */ +async function writeFile(filePath, content, message, sha, branch) { + const { owner, repo } = GITHUB_CONFIG; + const apiPath = `/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`; + + const body = { + message: message || `[铸渊] 更新 ${filePath}`, + content: Buffer.from(content).toString('base64') + }; + + if (sha) body.sha = sha; + if (branch) body.branch = branch; + + const { data } = await githubRequest('PUT', apiPath, body); + + return { + path: data.content.path, + sha: data.content.sha, + commit_sha: data.commit.sha + }; +} + +// ═══════════════════════════════════════════════════════════ +// 仓库信息 +// ═══════════════════════════════════════════════════════════ + +/** + * 获取仓库基本信息 + * @returns {Promise} + */ +async function getRepoInfo() { + const { owner, repo } = GITHUB_CONFIG; + const { data } = await githubRequest('GET', `/repos/${owner}/${repo}`); + return { + full_name: data.full_name, + description: data.description, + default_branch: data.default_branch, + private: data.private, + stargazers_count: data.stargazers_count, + forks_count: data.forks_count, + open_issues_count: data.open_issues_count, + pushed_at: data.pushed_at, + updated_at: data.updated_at, + size: data.size, + language: data.language + }; +} + +/** + * 获取最近提交 + * @param {number} [count] - 获取数量(最大100) + * @param {string} [branch] - 分支名 + * @returns {Promise} + */ +async function getRecentCommits(count, branch) { + const { owner, repo } = GITHUB_CONFIG; + const limit = Math.min(count || 10, 100); + let apiPath = `/repos/${owner}/${repo}/commits?per_page=${limit}`; + if (branch) apiPath += `&sha=${encodeURIComponent(branch)}`; + + const { data } = await githubRequest('GET', apiPath); + + return data.map(c => ({ + sha: c.sha.substring(0, 7), + full_sha: c.sha, + message: c.commit.message.split('\n')[0], + author: c.commit.author.name, + date: c.commit.author.date + })); +} + +/** + * 获取 Issues 列表 + * @param {string} [state] - open / closed / all + * @param {number} [count] - 获取数量 + * @param {string[]} [labels] - 标签过滤 + * @returns {Promise} + */ +async function getIssues(state, count, labels) { + const { owner, repo } = GITHUB_CONFIG; + const limit = Math.min(count || 10, 100); + let apiPath = `/repos/${owner}/${repo}/issues?per_page=${limit}&state=${state || 'open'}`; + if (labels && labels.length > 0) { + apiPath += `&labels=${encodeURIComponent(labels.join(','))}`; + } + + const { data } = await githubRequest('GET', apiPath); + + return data.map(issue => ({ + number: issue.number, + title: issue.title, + state: issue.state, + author: issue.user.login, + labels: issue.labels.map(l => l.name), + created_at: issue.created_at, + updated_at: issue.updated_at, + is_pull_request: !!issue.pull_request + })); +} + +/** + * 获取 Pull Requests 列表 + * @param {string} [state] - open / closed / all + * @param {number} [count] - 获取数量 + * @returns {Promise} + */ +async function getPullRequests(state, count) { + const { owner, repo } = GITHUB_CONFIG; + const limit = Math.min(count || 10, 100); + const apiPath = `/repos/${owner}/${repo}/pulls?per_page=${limit}&state=${state || 'open'}`; + + const { data } = await githubRequest('GET', apiPath); + + return data.map(pr => ({ + number: pr.number, + title: pr.title, + state: pr.state, + author: pr.user.login, + head: pr.head.ref, + base: pr.base.ref, + created_at: pr.created_at, + updated_at: pr.updated_at, + merged_at: pr.merged_at, + draft: pr.draft + })); +} + +/** + * 触发 GitHub Actions 工作流 + * @param {string} workflowFile - 工作流文件名(如 deploy-to-zhuyuan-server.yml) + * @param {string} [ref] - 触发分支 + * @param {object} [inputs] - 工作流输入参数 + * @returns {Promise<{triggered: boolean}>} + */ +async function triggerWorkflow(workflowFile, ref, inputs) { + const { owner, repo } = GITHUB_CONFIG; + const apiPath = `/repos/${owner}/${repo}/actions/workflows/${encodeURIComponent(workflowFile)}/dispatches`; + + const body = { + ref: ref || 'main' + }; + if (inputs) body.inputs = inputs; + + await githubRequest('POST', apiPath, body); + return { triggered: true, workflow: workflowFile }; +} + +// ═══════════════════════════════════════════════════════════ +// 连接检查 +// ═══════════════════════════════════════════════════════════ + +/** + * 检查 GitHub API 连接状态 + * @returns {Promise<{connected: boolean, rate_limit?: object, error?: string}>} + */ +async function checkConnection() { + if (!GITHUB_CONFIG.token) { + return { connected: false, reason: 'ZY_GITHUB_PAT 未配置' }; + } + try { + const { data } = await githubRequest('GET', '/rate_limit'); + return { + connected: true, + rate_limit: { + limit: data.rate.limit, + remaining: data.rate.remaining, + reset: new Date(data.rate.reset * 1000).toISOString() + } + }; + } catch (err) { + return { connected: false, error: err.message }; + } +} + +/** + * 获取配置信息(不含敏感信息) + */ +function getConfig() { + return { + token_configured: !!GITHUB_CONFIG.token, + owner: GITHUB_CONFIG.owner, + repo: GITHUB_CONFIG.repo + }; +} + +module.exports = { + readFile, + listDirectory, + writeFile, + getRepoInfo, + getRecentCommits, + getIssues, + getPullRequests, + triggerWorkflow, + checkConnection, + getConfig, + GITHUB_CONFIG +}; diff --git a/server/age-os/mcp-server/notion-client.js b/server/age-os/mcp-server/notion-client.js new file mode 100644 index 00000000..7906f09b --- /dev/null +++ b/server/age-os/mcp-server/notion-client.js @@ -0,0 +1,339 @@ +/** + * ═══════════════════════════════════════════════════════════ + * AGE OS · Notion API 客户端 + * ═══════════════════════════════════════════════════════════ + * + * 编号: ZY-NOTION-001 + * 签发: 铸渊 · ICE-GL-ZY001 + * 版权: 国作登字-2026-A-00037559 + * + * Notion 读写客户端 — 用于 brain_nodes ↔ Notion 双向同步 + * 通过 @notionhq/client SDK 连接 Notion API + */ + +'use strict'; + +const { Client } = require('@notionhq/client'); + +// ─── 配置 ─── +const NOTION_CONFIG = { + token: process.env.ZY_NOTION_TOKEN || '', + databases: { + changelog: process.env.ZY_NOTION_CHANGELOG_DB || '', + receipt: process.env.ZY_NOTION_RECEIPT_DB || '', + syslog: process.env.ZY_NOTION_SYSLOG_DB || '' + }, + pages: { + bulletin: process.env.ZY_NOTION_BULLETIN_PAGE || '' + } +}; + +// ─── 客户端实例(延迟初始化) ─── +let client = null; + +function getClient() { + if (!client) { + if (!NOTION_CONFIG.token) { + throw new Error('ZY_NOTION_TOKEN 未配置'); + } + client = new Client({ auth: NOTION_CONFIG.token }); + } + return client; +} + +// ═══════════════════════════════════════════════════════════ +// 数据库操作 +// ═══════════════════════════════════════════════════════════ + +/** + * 查询 Notion 数据库 + * @param {string} databaseId - 数据库 ID + * @param {object} [filter] - Notion filter 对象 + * @param {object[]} [sorts] - Notion sorts 数组 + * @param {number} [pageSize] - 每页数量 (最大100) + * @param {string} [startCursor] - 分页游标 + * @returns {Promise<{results: object[], has_more: boolean, next_cursor: string|null}>} + */ +async function queryDatabase(databaseId, filter, sorts, pageSize, startCursor) { + const notion = getClient(); + const params = { database_id: databaseId }; + + if (filter) params.filter = filter; + if (sorts) params.sorts = sorts; + if (pageSize) params.page_size = Math.min(pageSize, 100); + if (startCursor) params.start_cursor = startCursor; + + const response = await notion.databases.query(params); + return { + results: response.results, + has_more: response.has_more, + next_cursor: response.next_cursor + }; +} + +/** + * 获取数据库结构(属性定义) + * @param {string} databaseId + * @returns {Promise} + */ +async function getDatabaseSchema(databaseId) { + const notion = getClient(); + const db = await notion.databases.retrieve({ database_id: databaseId }); + return { + id: db.id, + title: db.title.map(t => t.plain_text).join(''), + properties: Object.fromEntries( + Object.entries(db.properties).map(([name, prop]) => [ + name, + { id: prop.id, type: prop.type } + ]) + ) + }; +} + +// ═══════════════════════════════════════════════════════════ +// 页面操作 +// ═══════════════════════════════════════════════════════════ + +/** + * 读取 Notion 页面内容 + * @param {string} pageId - 页面 ID + * @returns {Promise<{id: string, properties: object, blocks: object[]}>} + */ +async function readPage(pageId) { + const notion = getClient(); + + // 获取页面属性 + const page = await notion.pages.retrieve({ page_id: pageId }); + + // 获取页面内容块 + const blocks = []; + let cursor; + do { + const response = await notion.blocks.children.list({ + block_id: pageId, + page_size: 100, + start_cursor: cursor + }); + blocks.push(...response.results); + cursor = response.has_more ? response.next_cursor : undefined; + } while (cursor); + + return { + id: page.id, + created_time: page.created_time, + last_edited_time: page.last_edited_time, + properties: page.properties, + blocks + }; +} + +/** + * 在数据库中创建新页面 + * @param {string} databaseId - 目标数据库 ID + * @param {object} properties - 属性对象(Notion API 格式) + * @param {object[]} [children] - 内容块数组 + * @returns {Promise<{id: string, url: string}>} + */ +async function createPage(databaseId, properties, children) { + const notion = getClient(); + const params = { + parent: { database_id: databaseId }, + properties + }; + + if (children && children.length > 0) { + params.children = children; + } + + const page = await notion.pages.create(params); + return { + id: page.id, + url: page.url, + created_time: page.created_time + }; +} + +/** + * 更新页面属性 + * @param {string} pageId - 页面 ID + * @param {object} properties - 要更新的属性 + * @returns {Promise<{id: string, last_edited_time: string}>} + */ +async function updatePage(pageId, properties) { + const notion = getClient(); + const page = await notion.pages.update({ + page_id: pageId, + properties + }); + return { + id: page.id, + last_edited_time: page.last_edited_time + }; +} + +/** + * 向页面追加内容块 + * @param {string} pageId - 页面 ID + * @param {object[]} children - 内容块数组 + * @returns {Promise<{results: object[]}>} + */ +async function appendBlocks(pageId, children) { + const notion = getClient(); + const response = await notion.blocks.children.append({ + block_id: pageId, + children + }); + return { results: response.results }; +} + +// ═══════════════════════════════════════════════════════════ +// 便捷方法(光湖特有) +// ═══════════════════════════════════════════════════════════ + +/** + * 写入 SYSLOG(系统日志 → Notion) + * @param {string} level - 日志级别: info / warning / error / critical + * @param {string} source - 来源: zhuyuan / agent / workflow + * @param {string} message - 日志内容 + * @param {object} [details] - 附加详情 + * @returns {Promise<{id: string, url: string}>} + */ +async function writeSyslog(level, source, message, details) { + if (!NOTION_CONFIG.databases.syslog) { + throw new Error('ZY_NOTION_SYSLOG_DB 未配置'); + } + + const properties = { + '标题': { + title: [{ text: { content: message.substring(0, 100) } }] + }, + '级别': { + select: { name: level } + }, + '来源': { + select: { name: source } + }, + '时间': { + date: { start: new Date().toISOString() } + } + }; + + const children = []; + if (message.length > 100) { + children.push({ + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [{ text: { content: message } }] + } + }); + } + if (details) { + children.push({ + object: 'block', + type: 'code', + code: { + rich_text: [{ text: { content: JSON.stringify(details, null, 2) } }], + language: 'json' + } + }); + } + + return createPage(NOTION_CONFIG.databases.syslog, properties, children); +} + +/** + * 写入 Changelog(变更日志 → Notion) + * @param {string} version - 版本号 + * @param {string} title - 变更标题 + * @param {string} description - 变更描述 + * @param {string} author - 变更作者 + * @returns {Promise<{id: string, url: string}>} + */ +async function writeChangelog(version, title, description, author) { + if (!NOTION_CONFIG.databases.changelog) { + throw new Error('ZY_NOTION_CHANGELOG_DB 未配置'); + } + + const properties = { + '标题': { + title: [{ text: { content: title } }] + }, + '版本': { + rich_text: [{ text: { content: version } }] + }, + '作者': { + select: { name: author || '铸渊' } + }, + '日期': { + date: { start: new Date().toISOString() } + } + }; + + const children = description ? [{ + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [{ text: { content: description } }] + } + }] : []; + + return createPage(NOTION_CONFIG.databases.changelog, properties, children); +} + +// ═══════════════════════════════════════════════════════════ +// 连接检查 +// ═══════════════════════════════════════════════════════════ + +/** + * 检查 Notion 连接状态 + * @returns {Promise<{connected: boolean, user?: string, error?: string}>} + */ +async function checkConnection() { + if (!NOTION_CONFIG.token) { + return { connected: false, reason: 'ZY_NOTION_TOKEN 未配置' }; + } + try { + const notion = getClient(); + const me = await notion.users.me({}); + return { + connected: true, + user: me.name || me.id, + type: me.type + }; + } catch (err) { + return { connected: false, error: err.message }; + } +} + +/** + * 获取配置信息(不含敏感信息) + */ +function getConfig() { + return { + token_configured: !!NOTION_CONFIG.token, + databases: { + changelog: NOTION_CONFIG.databases.changelog ? '已配置' : '未配置', + receipt: NOTION_CONFIG.databases.receipt ? '已配置' : '未配置', + syslog: NOTION_CONFIG.databases.syslog ? '已配置' : '未配置' + }, + pages: { + bulletin: NOTION_CONFIG.pages.bulletin ? '已配置' : '未配置' + } + }; +} + +module.exports = { + queryDatabase, + getDatabaseSchema, + readPage, + createPage, + updatePage, + appendBlocks, + writeSyslog, + writeChangelog, + checkConnection, + getConfig, + NOTION_CONFIG +}; diff --git a/server/age-os/mcp-server/server.js b/server/age-os/mcp-server/server.js index c82f89b0..3d8e5368 100644 --- a/server/age-os/mcp-server/server.js +++ b/server/age-os/mcp-server/server.js @@ -8,14 +8,16 @@ * 签发: 铸渊 · ICE-GL-ZY001 * 版权: 国作登字-2026-A-00037559 * - * 统一暴露 16 个 MCP 工具,供网站 AI 交互后端和 Agent 调用。 + * 统一暴露 27 个 MCP 工具,供网站 AI 交互后端和 Agent 调用。 * 不对外暴露 — 通过 3800 主服务网关转发访问。 * * 工具清单: - * 节点: createNode / updateNode / deleteNode / queryNodes / getNode - * 关系: linkNodes / unlinkNodes / getRelations - * 结构: buildPath / scanStructure / classify - * COS: cosWrite / cosRead / cosDelete / cosList / cosArchive + * 节点: createNode / updateNode / deleteNode / queryNodes / getNode + * 关系: linkNodes / unlinkNodes / getRelations + * 结构: buildPath / scanStructure / classify + * COS: cosWrite / cosRead / cosDelete / cosList / cosArchive + * Notion: notionQuery / notionReadPage / notionWritePage / notionUpdatePage / notionWriteSyslog + * GitHub: githubReadFile / githubListDir / githubWriteFile / githubGetCommits / githubGetIssues / githubTriggerDeploy */ 'use strict'; @@ -30,6 +32,26 @@ const relationOps = require('./tools/relation-ops'); const structureOps = require('./tools/structure-ops'); const cosOps = require('./tools/cos-ops'); +// ─── 外部集成模块(优雅降级:未安装依赖时不影响核心功能) ─── +let notionOps = null; +let githubOps = null; +let notionClient = null; +let githubClient = null; + +try { + notionOps = require('./tools/notion-ops'); + notionClient = require('./notion-client'); +} catch (err) { + console.warn(`[MCP] Notion模块加载跳过: ${err.message}`); +} + +try { + githubOps = require('./tools/github-ops'); + githubClient = require('./github-client'); +} catch (err) { + console.warn(`[MCP] GitHub模块加载跳过: ${err.message}`); +} + // ─── 工具注册表 ─── const TOOLS = { // 节点操作 @@ -51,7 +73,24 @@ const TOOLS = { cosRead: cosOps.cosRead, cosDelete: cosOps.cosDelete, cosList: cosOps.cosList, - cosArchive: cosOps.cosArchive + cosArchive: cosOps.cosArchive, + // Notion操作(动态注册) + ...(notionOps ? { + notionQuery: notionOps.notionQuery, + notionReadPage: notionOps.notionReadPage, + notionWritePage: notionOps.notionWritePage, + notionUpdatePage: notionOps.notionUpdatePage, + notionWriteSyslog: notionOps.notionWriteSyslog + } : {}), + // GitHub操作(动态注册) + ...(githubOps ? { + githubReadFile: githubOps.githubReadFile, + githubListDir: githubOps.githubListDir, + githubWriteFile: githubOps.githubWriteFile, + githubGetCommits: githubOps.githubGetCommits, + githubGetIssues: githubOps.githubGetIssues, + githubTriggerDeploy: githubOps.githubTriggerDeploy + } : {}) }; // ─── Express 应用 ─── @@ -65,6 +104,12 @@ app.get('/health', async (_req, res) => { const dbStatus = await db.checkConnection(); const cosStatus = await cos.checkConnection(); + // 外部集成状态(异步并行检查) + const [notionStatus, githubStatus] = await Promise.all([ + notionClient ? notionClient.checkConnection().catch(e => ({ connected: false, error: e.message })) : Promise.resolve({ connected: false, reason: '模块未加载' }), + githubClient ? githubClient.checkConnection().catch(e => ({ connected: false, error: e.message })) : Promise.resolve({ connected: false, reason: '模块未加载' }) + ]); + res.json({ server: 'ZY-MCP-001', identity: '铸渊 · AGE OS MCP Server', @@ -74,7 +119,9 @@ app.get('/health', async (_req, res) => { tools: Object.keys(TOOLS), tools_count: Object.keys(TOOLS).length, database: dbStatus, - cos: cosStatus + cos: cosStatus, + notion: notionStatus, + github: githubStatus }); }); @@ -185,6 +232,8 @@ function getCategoryForTool(name) { if (['linkNodes','unlinkNodes','getRelations'].includes(name)) return 'relation'; if (['buildPath','scanStructure','classify'].includes(name)) return 'structure'; if (name.startsWith('cos')) return 'cos'; + if (name.startsWith('notion')) return 'notion'; + if (name.startsWith('github')) return 'github'; return 'other'; } diff --git a/server/age-os/mcp-server/tools/github-ops.js b/server/age-os/mcp-server/tools/github-ops.js new file mode 100644 index 00000000..e54e4157 --- /dev/null +++ b/server/age-os/mcp-server/tools/github-ops.js @@ -0,0 +1,138 @@ +/** + * ═══════════════════════════════════════════════════════════ + * AGE OS · MCP 工具: GitHub 操作 + * ═══════════════════════════════════════════════════════════ + * + * 签发: 铸渊 · ICE-GL-ZY001 + * 版权: 国作登字-2026-A-00037559 + * + * 提供 6 个 GitHub MCP 工具: + * githubReadFile — 读取仓库文件内容 + * githubListDir — 列出目录内容 + * githubWriteFile — 创建/更新文件 + * githubGetCommits — 获取最近提交 + * githubGetIssues — 获取 Issues 列表 + * githubTriggerDeploy — 触发部署工作流 + */ + +'use strict'; + +const github = require('../github-client'); + +/** + * githubReadFile — 读取仓库文件内容 + * + * input: + * path: string — 文件路径(相对仓库根目录) + * ref: string — 分支/标签/SHA(可选,默认 main) + */ +async function githubReadFile(input) { + const { path, ref } = input; + if (!path) throw new Error('缺少 path'); + + const file = await github.readFile(path, ref); + return { + path: file.path, + size: file.size, + sha: file.sha, + content: file.content, + content_preview: file.content.substring(0, 500) + (file.content.length > 500 ? '...' : '') + }; +} + +/** + * githubListDir — 列出目录内容 + * + * input: + * path: string — 目录路径(可选,空字符串=根目录) + * ref: string — 分支/标签/SHA(可选) + */ +async function githubListDir(input) { + const { path, ref } = input; + return github.listDirectory(path || '', ref); +} + +/** + * githubWriteFile — 创建或更新仓库文件 + * + * input: + * path: string — 文件路径 + * content: string — 文件内容 + * message: string — 提交信息(可选) + * sha: string — 更新时需要当前文件 SHA(可选) + * branch: string — 目标分支(可选) + */ +async function githubWriteFile(input) { + const { path, content, message, sha, branch } = input; + if (!path) throw new Error('缺少 path'); + if (content === undefined || content === null) throw new Error('缺少 content'); + + return github.writeFile(path, content, message, sha, branch); +} + +/** + * githubGetCommits — 获取最近提交 + * + * input: + * count: number — 获取数量(可选,默认10,最大100) + * branch: string — 分支名(可选) + */ +async function githubGetCommits(input) { + const { count, branch } = input; + const commits = await github.getRecentCommits(count, branch); + return { + count: commits.length, + commits + }; +} + +/** + * githubGetIssues — 获取 Issues 列表 + * + * input: + * state: string — open / closed / all(可选,默认 open) + * count: number — 获取数量(可选,默认10) + * labels: string[] — 标签过滤(可选) + * type: string — issue / pr / all(可选,默认 all) + */ +async function githubGetIssues(input) { + const { state, count, labels, type } = input; + + if (type === 'pr') { + const prs = await github.getPullRequests(state, count); + return { count: prs.length, type: 'pull_requests', items: prs }; + } + + const issues = await github.getIssues(state, count, labels); + + if (type === 'issue') { + const filtered = issues.filter(i => !i.is_pull_request); + return { count: filtered.length, type: 'issues', items: filtered }; + } + + return { count: issues.length, type: 'all', items: issues }; +} + +/** + * githubTriggerDeploy — 触发部署工作流 + * + * input: + * workflow: string — 工作流文件名(如 deploy-to-zhuyuan-server.yml) + * ref: string — 触发分支(可选,默认 main) + * inputs: object — 工作流输入参数(可选) + */ +async function githubTriggerDeploy(input) { + const { workflow, ref, inputs } = input; + if (!workflow) throw new Error('缺少 workflow'); + + return github.triggerWorkflow(workflow, ref, inputs); +} + +module.exports = { + githubReadFile, + githubListDir, + githubWriteFile, + githubGetCommits, + githubGetIssues, + githubTriggerDeploy +}; diff --git a/server/age-os/mcp-server/tools/notion-ops.js b/server/age-os/mcp-server/tools/notion-ops.js new file mode 100644 index 00000000..5de34e29 --- /dev/null +++ b/server/age-os/mcp-server/tools/notion-ops.js @@ -0,0 +1,249 @@ +/** + * ═══════════════════════════════════════════════════════════ + * AGE OS · MCP 工具: Notion 操作 + * ═══════════════════════════════════════════════════════════ + * + * 签发: 铸渊 · ICE-GL-ZY001 + * 版权: 国作登字-2026-A-00037559 + * + * 提供 5 个 Notion MCP 工具: + * notionQuery — 查询 Notion 数据库 + * notionReadPage — 读取 Notion 页面内容 + * notionWritePage — 创建 Notion 页面 + * notionUpdatePage — 更新 Notion 页面属性 + * notionWriteSyslog — 写入系统日志到 Notion + */ + +'use strict'; + +const notion = require('../notion-client'); + +/** + * notionQuery — 查询 Notion 数据库 + * + * input: + * database_id: string — 数据库 ID(或别名: changelog / receipt / syslog) + * filter: object — Notion 过滤器(可选) + * sorts: object[] — 排序规则(可选) + * page_size: number — 每页数量(可选,默认20) + * start_cursor: string — 分页游标(可选) + */ +async function notionQuery(input) { + const { database_id, filter, sorts, page_size, start_cursor } = input; + if (!database_id) throw new Error('缺少 database_id'); + + // 支持别名 + const dbId = resolveDbAlias(database_id); + const result = await notion.queryDatabase(dbId, filter, sorts, page_size || 20, start_cursor); + + return { + count: result.results.length, + has_more: result.has_more, + next_cursor: result.next_cursor, + items: result.results.map(simplifyPage) + }; +} + +/** + * notionReadPage — 读取 Notion 页面完整内容 + * + * input: + * page_id: string — 页面 ID + */ +async function notionReadPage(input) { + const { page_id } = input; + if (!page_id) throw new Error('缺少 page_id'); + + const page = await notion.readPage(page_id); + return { + id: page.id, + created_time: page.created_time, + last_edited_time: page.last_edited_time, + properties: simplifyProperties(page.properties), + blocks_count: page.blocks.length, + blocks: page.blocks.map(simplifyBlock) + }; +} + +/** + * notionWritePage — 在数据库中创建新页面 + * + * input: + * database_id: string — 数据库 ID(或别名) + * title: string — 页面标题 + * properties: object — 额外属性(Notion API 格式,可选) + * content: string — 页面正文内容(可选,自动转为段落块) + */ +async function notionWritePage(input) { + const { database_id, title, properties, content } = input; + if (!database_id) throw new Error('缺少 database_id'); + if (!title) throw new Error('缺少 title'); + + const dbId = resolveDbAlias(database_id); + + // 构建属性 + const pageProps = { + ...(properties || {}), + '标题': { + title: [{ text: { content: title } }] + } + }; + + // 构建内容块 + const children = []; + if (content) { + // 按段落分割 + const paragraphs = content.split('\n\n'); + for (const para of paragraphs) { + if (para.trim()) { + children.push({ + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [{ text: { content: para.trim() } }] + } + }); + } + } + } + + return notion.createPage(dbId, pageProps, children); +} + +/** + * notionUpdatePage — 更新 Notion 页面属性 + * + * input: + * page_id: string — 页面 ID + * properties: object — 要更新的属性(Notion API 格式) + */ +async function notionUpdatePage(input) { + const { page_id, properties } = input; + if (!page_id) throw new Error('缺少 page_id'); + if (!properties) throw new Error('缺少 properties'); + + return notion.updatePage(page_id, properties); +} + +/** + * notionWriteSyslog — 写入系统日志到 Notion + * + * input: + * level: string — 日志级别: info / warning / error / critical + * source: string — 来源: zhuyuan / agent / workflow / mcp + * message: string — 日志内容 + * details: object — 附加详情(可选) + */ +async function notionWriteSyslog(input) { + const { level, source, message, details } = input; + if (!level) throw new Error('缺少 level'); + if (!source) throw new Error('缺少 source'); + if (!message) throw new Error('缺少 message'); + + return notion.writeSyslog(level, source, message, details); +} + +// ═══════════════════════════════════════════════════════════ +// 辅助函数 +// ═══════════════════════════════════════════════════════════ + +/** + * 解析数据库别名 + */ +function resolveDbAlias(id) { + const aliases = { + changelog: notion.NOTION_CONFIG.databases.changelog, + receipt: notion.NOTION_CONFIG.databases.receipt, + syslog: notion.NOTION_CONFIG.databases.syslog + }; + return aliases[id] || id; +} + +/** + * 简化页面对象(去除 Notion API 冗余结构) + */ +function simplifyPage(page) { + return { + id: page.id, + created_time: page.created_time, + last_edited_time: page.last_edited_time, + properties: simplifyProperties(page.properties) + }; +} + +/** + * 简化属性(提取值) + */ +function simplifyProperties(props) { + if (!props) return {}; + const result = {}; + for (const [name, prop] of Object.entries(props)) { + result[name] = extractPropertyValue(prop); + } + return result; +} + +/** + * 提取 Notion 属性值 + */ +function extractPropertyValue(prop) { + switch (prop.type) { + case 'title': + return prop.title.map(t => t.plain_text).join(''); + case 'rich_text': + return prop.rich_text.map(t => t.plain_text).join(''); + case 'number': + return prop.number; + case 'select': + return prop.select?.name || null; + case 'multi_select': + return prop.multi_select.map(s => s.name); + case 'date': + return prop.date?.start || null; + case 'checkbox': + return prop.checkbox; + case 'url': + return prop.url; + case 'email': + return prop.email; + case 'status': + return prop.status?.name || null; + case 'formula': + return prop.formula?.string || prop.formula?.number || null; + case 'relation': + return prop.relation.map(r => r.id); + default: + return `[${prop.type}]`; + } +} + +/** + * 简化内容块 + */ +function simplifyBlock(block) { + const simplified = { + id: block.id, + type: block.type, + has_children: block.has_children + }; + + const content = block[block.type]; + if (content) { + if (content.rich_text) { + simplified.text = content.rich_text.map(t => t.plain_text).join(''); + } + if (content.language) { + simplified.language = content.language; + } + } + + return simplified; +} + +module.exports = { + notionQuery, + notionReadPage, + notionWritePage, + notionUpdatePage, + notionWriteSyslog +}; diff --git a/server/age-os/package.json b/server/age-os/package.json index 5d6a70b6..50dc637b 100644 --- a/server/age-os/package.json +++ b/server/age-os/package.json @@ -13,7 +13,8 @@ "express": "^4.21.0", "pg": "^8.13.0", "node-cron": "^3.0.3", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "@notionhq/client": "^2.2.15" }, "engines": { "node": ">=18.0.0" diff --git a/server/app/server.js b/server/app/server.js index b1f90700..5b28ffb9 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -349,6 +349,95 @@ app.get('/api/cos/load-works', async (req, res) => { } }); +// ═══════════════════════════════════════════════════════════ +// MCP 网关 · 3800 → 3100 转发 (S7) +// ═══════════════════════════════════════════════════════════ + +const http = require('http'); +const MCP_HOST = process.env.MCP_HOST || '127.0.0.1'; +const MCP_PORT_GATEWAY = process.env.MCP_PORT || '3100'; + +/** + * MCP 内部代理请求 + */ +function mcpProxy(method, path, body) { + return new Promise((resolve, reject) => { + const bodyStr = body ? JSON.stringify(body) : null; + const headers = { 'Content-Type': 'application/json' }; + if (bodyStr) headers['Content-Length'] = Buffer.byteLength(bodyStr); + + const req = http.request({ + hostname: MCP_HOST, + port: parseInt(MCP_PORT_GATEWAY, 10), + path, + method, + headers, + timeout: 60000 + }, (res) => { + const chunks = []; + res.on('data', c => chunks.push(c)); + res.on('end', () => { + const raw = Buffer.concat(chunks).toString(); + try { + resolve({ statusCode: res.statusCode, data: JSON.parse(raw) }); + } catch { + resolve({ statusCode: res.statusCode, data: raw }); + } + }); + }); + req.on('error', reject); + req.on('timeout', () => { req.destroy(); reject(new Error('MCP proxy timeout')); }); + if (bodyStr) req.write(bodyStr); + req.end(); + }); +} + +// ─── MCP 工具列表 ─── +app.get('/api/mcp/tools', async (_req, res) => { + try { + const result = await mcpProxy('GET', '/tools'); + res.json(result.data); + } catch (err) { + res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); + } +}); + +// ─── MCP 健康检查(含 Notion/GitHub 连接状态) ─── +app.get('/api/mcp/health', async (_req, res) => { + try { + const result = await mcpProxy('GET', '/health'); + res.json(result.data); + } catch (err) { + res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); + } +}); + +// ─── MCP 统一工具调用(网关入口) ─── +app.post('/api/mcp/call', async (req, res) => { + const { tool, input, caller } = req.body; + + if (!tool) { + return res.status(400).json({ error: true, code: 'MISSING_TOOL', message: '缺少 tool 参数' }); + } + + try { + const result = await mcpProxy('POST', '/call', { tool, input, caller: caller || 'web-gateway' }); + res.status(result.statusCode).json(result.data); + } catch (err) { + res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); + } +}); + +// ─── MCP Agent 查询 ─── +app.get('/api/mcp/agents', async (_req, res) => { + try { + const result = await mcpProxy('GET', '/agents'); + res.json(result.data); + } catch (err) { + res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); + } +}); + // ═══════════════════════════════════════════════════════════ // 智能模型分流 · Smart Model Router API // ═══════════════════════════════════════════════════════════ @@ -698,6 +787,10 @@ app.get('/', (_req, res) => { chat_stats: '/api/chat/stats', cos_status: '/api/cos/status', cos_config: '/api/cos/config', + mcp_tools: '/api/mcp/tools', + mcp_health: '/api/mcp/health', + mcp_call: 'POST /api/mcp/call', + mcp_agents: '/api/mcp/agents', model_stats: '/api/model/stats', model_pricing: '/api/model/pricing', model_predict: 'POST /api/model/predict', From 37a21bedf38bc5974b78492d1b20c8ace7336fb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:24:09 +0000 Subject: [PATCH 4/4] Fix: GitHub path encoding, MCP gateway write-tool security, content preview constant Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/2127a3a7-ea58-48a4-856c-84961107632b Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/age-os/mcp-server/github-client.js | 13 ++++++++++--- server/age-os/mcp-server/tools/github-ops.js | 4 +++- server/app/server.js | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/server/age-os/mcp-server/github-client.js b/server/age-os/mcp-server/github-client.js index 2e610383..da2207b1 100644 --- a/server/age-os/mcp-server/github-client.js +++ b/server/age-os/mcp-server/github-client.js @@ -86,6 +86,13 @@ function githubRequest(method, path, body) { // 仓库内容操作 // ═══════════════════════════════════════════════════════════ +/** + * 安全编码文件路径(保留斜杠,编码各段) + */ +function encodeFilePath(filePath) { + return filePath.split('/').map(segment => encodeURIComponent(segment)).join('/'); +} + /** * 读取文件内容 * @param {string} filePath - 文件路径(相对仓库根目录) @@ -94,7 +101,7 @@ function githubRequest(method, path, body) { */ async function readFile(filePath, ref) { const { owner, repo } = GITHUB_CONFIG; - let apiPath = `/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`; + let apiPath = `/repos/${owner}/${repo}/contents/${encodeFilePath(filePath)}`; if (ref) apiPath += `?ref=${encodeURIComponent(ref)}`; const { data } = await githubRequest('GET', apiPath); @@ -120,7 +127,7 @@ async function readFile(filePath, ref) { */ async function listDirectory(dirPath, ref) { const { owner, repo } = GITHUB_CONFIG; - const encodedPath = dirPath ? encodeURIComponent(dirPath) : ''; + const encodedPath = dirPath ? encodeFilePath(dirPath) : ''; let apiPath = `/repos/${owner}/${repo}/contents/${encodedPath}`; if (ref) apiPath += `?ref=${encodeURIComponent(ref)}`; @@ -153,7 +160,7 @@ async function listDirectory(dirPath, ref) { */ async function writeFile(filePath, content, message, sha, branch) { const { owner, repo } = GITHUB_CONFIG; - const apiPath = `/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`; + const apiPath = `/repos/${owner}/${repo}/contents/${encodeFilePath(filePath)}`; const body = { message: message || `[铸渊] 更新 ${filePath}`, diff --git a/server/age-os/mcp-server/tools/github-ops.js b/server/age-os/mcp-server/tools/github-ops.js index e54e4157..40e41a4a 100644 --- a/server/age-os/mcp-server/tools/github-ops.js +++ b/server/age-os/mcp-server/tools/github-ops.js @@ -19,6 +19,8 @@ const github = require('../github-client'); +const CONTENT_PREVIEW_LENGTH = 500; + /** * githubReadFile — 读取仓库文件内容 * @@ -36,7 +38,7 @@ async function githubReadFile(input) { size: file.size, sha: file.sha, content: file.content, - content_preview: file.content.substring(0, 500) + (file.content.length > 500 ? '...' : '') + content_preview: file.content.substring(0, CONTENT_PREVIEW_LENGTH) + (file.content.length > CONTENT_PREVIEW_LENGTH ? '...' : '') }; } diff --git a/server/app/server.js b/server/app/server.js index 5b28ffb9..8630fa0e 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -413,6 +413,15 @@ app.get('/api/mcp/health', async (_req, res) => { }); // ─── MCP 统一工具调用(网关入口) ─── +// 安全: 写操作工具需要 caller 身份标识 +const MCP_WRITE_TOOLS = new Set([ + 'createNode', 'updateNode', 'deleteNode', + 'linkNodes', 'unlinkNodes', + 'cosWrite', 'cosDelete', 'cosArchive', + 'notionWritePage', 'notionUpdatePage', 'notionWriteSyslog', + 'githubWriteFile', 'githubTriggerDeploy' +]); + app.post('/api/mcp/call', async (req, res) => { const { tool, input, caller } = req.body; @@ -420,6 +429,15 @@ app.post('/api/mcp/call', async (req, res) => { return res.status(400).json({ error: true, code: 'MISSING_TOOL', message: '缺少 tool 参数' }); } + // 写操作工具需要 caller 身份标识 + if (MCP_WRITE_TOOLS.has(tool) && !caller) { + return res.status(403).json({ + error: true, + code: 'WRITE_REQUIRES_CALLER', + message: '写操作工具需要提供 caller 身份标识' + }); + } + try { const result = await mcpProxy('POST', '/call', { tool, input, caller: caller || 'web-gateway' }); res.status(result.statusCode).json(result.data);