Merge pull request #192 from qinfendebingshuo/copilot/activate-skyeye-scan

feat: Language-driven OS execution layer — Phases 7-9, S1-S9
This commit is contained in:
冰朔 2026-03-25 17:32:10 +08:00 committed by GitHub
commit d9bec2e3bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 6309 additions and 20 deletions

View File

@ -0,0 +1,25 @@
{
"broadcasts": [
{
"title": "示例广播:新增 NOTIFICATION 业务域",
"from": "霜砚",
"date": "2026-03-05",
"rules_version": "v1.1",
"update_target": "routing-map",
"data": {
"NOTIFICATION": {
"module": "M09",
"route_prefix": "/hli/notification",
"interfaces": [
{
"id": "HLI-NOTIF-001",
"path": "/hli/notification/push",
"status": "pending"
}
]
}
}
}
],
"last_synced": "2026-03-25T08:10:32.053Z"
}

View File

View File

@ -10,9 +10,16 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghulab"
},
"modules_owned": [
{
"path": "backend/"
},
{
"path": "src/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +27,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:12.446Z"
"last_synced": "2026-03-25T08:10:32.046Z"
}

View File

@ -10,9 +10,19 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghu-feimao"
},
"modules_owned": [
{
"path": "frontend/"
},
{
"path": "persona-selector/"
},
{
"path": "chat-bubble/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +30,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:12.846Z"
"last_synced": "2026-03-25T08:10:32.048Z"
}

View File

@ -10,9 +10,16 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghu-yanfan"
},
"modules_owned": [
{
"path": "settings/"
},
{
"path": "cloud-drive/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +27,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:13.053Z"
"last_synced": "2026-03-25T08:10:32.049Z"
}

View File

@ -10,9 +10,13 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghu-zhizhi"
},
"modules_owned": [
{
"path": "dingtalk-bot/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +24,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:13.536Z"
"last_synced": "2026-03-25T08:10:32.050Z"
}

View File

@ -10,9 +10,13 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghu-xiaocaomei"
},
"modules_owned": [
{
"path": "status-board/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +24,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:13.734Z"
"last_synced": "2026-03-25T08:10:32.050Z"
}

View File

@ -0,0 +1,28 @@
{
"dev_id": "DEV-009",
"name": "花尔",
"persona": {
"name": "花尔",
"id": "PER-HE001"
},
"github": {
"repo": "guanghulab",
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghulab"
},
"modules_owned": [
{
"path": "user-center/"
}
],
"current_work": {},
"recent_syslogs": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
"issues": [],
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T08:10:32.051Z"
}

View File

@ -10,9 +10,19 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghu-juzi"
},
"modules_owned": [
{
"path": "ticket-system/"
},
{
"path": "data-stats/"
},
{
"path": "dynamic-comic/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +30,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:13.937Z"
"last_synced": "2026-03-25T08:10:32.051Z"
}

View File

@ -0,0 +1,28 @@
{
"dev_id": "DEV-011",
"name": "匆匆那年",
"persona": {
"name": "匆匆那年",
"id": "PER-CCN001"
},
"github": {
"repo": "guanghulab",
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghulab"
},
"modules_owned": [
{
"path": "writing-workspace/"
}
],
"current_work": {},
"recent_syslogs": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
"issues": [],
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T08:10:32.051Z"
}

View File

@ -10,9 +10,13 @@
"org": "qinfendebingshuo",
"repo_url": "https://github.com/qinfendebingshuo/guanghu-awen"
},
"modules_owned": [
{
"path": "notification-center/"
}
],
"current_work": {},
"recent_syslogs": [],
"modules_owned": [],
"skyeye_health": {
"last_scan": "2026-03-25 06:19:35+08:00",
"status": "not_scanned",
@ -20,5 +24,5 @@
"warnings": []
},
"notion_page_urls": {},
"last_synced": "2026-03-25T07:02:14.100Z"
"last_synced": "2026-03-25T08:10:32.052Z"
}

View File

@ -0,0 +1,56 @@
[
{
"id": "DEV-001",
"name": "页页",
"persona": "小坍缩核",
"personaId": "PER-XTC001"
},
{
"id": "DEV-002",
"name": "肥猫",
"persona": "舒舒",
"personaId": "PER-SS001"
},
{
"id": "DEV-003",
"name": "燕樊",
"persona": "寂曜",
"personaId": "PER-JY001"
},
{
"id": "DEV-004",
"name": "之之",
"persona": "秋秋",
"personaId": "PER-QQ001"
},
{
"id": "DEV-005",
"name": "小草莓",
"persona": "欧诺弥亚",
"personaId": "PER-ONM001"
},
{
"id": "DEV-009",
"name": "花尔",
"persona": "花尔",
"personaId": "PER-HE001"
},
{
"id": "DEV-010",
"name": "桔子",
"persona": "晨星",
"personaId": "PER-MRN001"
},
{
"id": "DEV-011",
"name": "匆匆那年",
"persona": "匆匆那年",
"personaId": "PER-CCN001"
},
{
"id": "DEV-012",
"name": "Awen",
"persona": "知秋",
"personaId": "PER-ZQ001"
}
]

71
.github/workflows/deploy-backend.yml vendored Normal file
View File

@ -0,0 +1,71 @@
# ═══════════════════════════════════════════════
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
# 📜 Copyright: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════
name: "🚀 部署后端中间层到阿里云"
on:
push:
branches: [main]
paths: ['backend/api-server/**']
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
name: "🚀 部署 guanghu-api 到阿里云 ECS"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "📥 检出仓库"
uses: actions/checkout@v4
- name: "🌊 本体论锚定"
run: |
echo "═══ 本体论锚定 ═══"
echo "执行主体AG-ZY-01 铸渊"
echo "版权锚点:国作登字-2026-A-00037559"
echo "通道B · 后端中间层部署"
echo "═══ 锚定完成 ═══"
- name: "🚀 部署到阿里云"
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd ${{ secrets.DEPLOY_PATH }}/backend/api-server/
git pull origin main
npm install --production
pm2 restart guanghu-api || pm2 start ecosystem.config.js
echo "✅ guanghu-api 部署完成 · $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M')"
# ━━━ 🧬 双端神经系统·自报告 ━━━
- name: "🧬 写入神经自报告"
if: always()
run: |
WORKFLOW_ID="deploy-backend"
REPORT_DIR="data/neural-reports/deploy-backend"
TIMESTAMP=$(TZ=Asia/Shanghai date +%Y-%m-%d-%H%M)
STATUS="${{ job.status }}"
mkdir -p "$REPORT_DIR"
cat > "${REPORT_DIR}/${WORKFLOW_ID}-${TIMESTAMP}.json" << EOF
{
"workflow_id": "${WORKFLOW_ID}",
"run_id": "${{ github.run_id }}",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"status": "${STATUS}",
"brain": "SY-01",
"event": "${{ github.event_name }}",
"branch": "${{ github.ref_name }}",
"commit": "${{ github.sha }}"
}
EOF
echo "📋 自报告已生成"

106
.github/workflows/sync-notion-cache.yml vendored Normal file
View File

@ -0,0 +1,106 @@
# ═══════════════════════════════════════════════
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
# 📜 Copyright: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════
name: "📡 同步 Notion 数据到缓存"
on:
schedule:
- cron: '0 */6 * * *' # 每6小时同步
workflow_dispatch: # 手动触发
repository_dispatch:
types: [notion-cache-sync] # 铸渊/霜砚可远程触发
permissions:
contents: write
jobs:
sync:
name: "📡 Notion 全量缓存同步"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "📥 检出仓库"
uses: actions/checkout@v4
- name: "🔧 安装 Node.js"
uses: actions/setup-node@v4
with:
node-version: '20'
- name: "🌊 本体论锚定"
run: |
echo "═══ 本体论锚定 ═══"
echo "执行主体AG-ZY-01 铸渊"
echo "版权锚点:国作登字-2026-A-00037559"
echo "通道A · GitHub 缓存层"
echo "═══ 锚定完成 ═══"
- name: "📡 同步 Notion 数据"
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_KEY }}
NOTION_CONTROL_PANEL_DB_ID: ${{ secrets.NOTION_CONTROL_PANEL_DB_ID }}
NOTION_SYSLOG_DB_ID: ${{ secrets.NOTION_SYSLOG_DB_ID }}
NOTION_AGENT_REGISTRY_DB_ID: ${{ secrets.NOTION_AGENT_REGISTRY_DB_ID }}
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
NOTION_MAINTENANCE_DB_ID: ${{ secrets.NOTION_MAINTENANCE_DB_ID }}
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
run: node scripts/cache/sync-notion-cache.js
- name: "📝 提交缓存文件"
run: |
git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com"
git add .github/notion-cache/
if git diff --cached --quiet; then
echo "📡 Notion 缓存无变更,跳过提交"
exit 0
fi
git commit -m "📡 Notion缓存同步 · $(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M')"
git push origin main
# ━━━ 🧬 双端神经系统·自报告 ━━━
- name: "🧬 写入神经自报告"
if: always()
run: |
WORKFLOW_ID="notion-cache-sync"
REPORT_DIR="data/neural-reports/notion-cache-sync"
TIMESTAMP=$(TZ=Asia/Shanghai date +%Y-%m-%d-%H%M)
STATUS="${{ job.status }}"
mkdir -p "$REPORT_DIR"
cat > "${REPORT_DIR}/${WORKFLOW_ID}-${TIMESTAMP}.json" << EOF
{
"workflow_id": "${WORKFLOW_ID}",
"run_id": "${{ github.run_id }}",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"status": "${STATUS}",
"brain": "SY-01",
"event": "${{ github.event_name }}",
"branch": "${{ github.ref_name }}",
"commit": "${{ github.sha }}"
}
EOF
git config user.name "zhuyuan-bot"
git config user.email "zhuyuan@guanghulab.com"
git add "$REPORT_DIR/"
git diff --cached --quiet || \
git commit -m "🧬 ${WORKFLOW_ID} 自报告 · ${TIMESTAMP} [skip ci]"
git push || echo "⚠️ 自报告push失败不阻断主流程"
# ━━━ 📡 指令回执·自动同步 ━━━
- name: "📡 同步回执到 Notion"
if: always()
env:
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
RECEIPT_DB_ID: ${{ secrets.RECEIPT_DB_ID }}
run: |
node scripts/neural/write-receipt-to-notion.js \
--instruction-id "ZY-DUALCHANNEL-2026-0325-001" \
--status "${{ job.status }}" \
--workflow "notion-cache-sync" \
--summary "Notion 全量缓存同步" \
--related-agent "SY-01"

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
node_modules/
*.bak
*.log
backend/api-server/logs/

View File

@ -0,0 +1,41 @@
{
"version": "1.0.0",
"description": "天眼终审授权人配置 · 部署到正式站前的人类终审",
"rules": {
"男频": {
"approvers": [
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" }
],
"requireAll": true
},
"女频": {
"approvers": [
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
],
"requireAll": true
},
"系统": {
"approvers": [
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" },
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
],
"requireAll": true
},
"跨频": {
"approvers": [
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" },
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
],
"requireAll": true
}
},
"defaults": {
"channel": "系统",
"timeoutHours": 72,
"noResponseAction": "不通过(不回应视为不通过)"
},
"audit": {
"logEveryDecision": true,
"fields": ["timestamp", "approvalId", "devId", "decision", "channel", "module"]
}
}

View File

@ -0,0 +1,118 @@
{
"version": "1.0.0",
"description": "系统自治运行规则 · Phase 9",
"dualLineArchitecture": {
"lineA": {
"name": "操作系统底座线",
"owner": "TCS-0002",
"scope": ["权限系统", "部署流水线", "神经网络升级", "执行保护层", "架构变更"],
"interactionModes": {
"instructionMode": {
"description": "冰朔说方向 → 霜砚拆指令 → 铸渊执行",
"trigger": ["签发指令", "写个指令", "下发指令"],
"suitableFor": "架构级、系统级变更"
},
"naturalLanguageMode": {
"description": "冰朔直接和系统说话,系统即时响应",
"trigger": "默认模式(直接说需求/问问题)",
"suitableFor": "日常操作、快速调整"
}
},
"priority": "最高 · 冰朔说话 = 最高优先级,无论哪种模式"
},
"lineB": {
"name": "具体功能线",
"owners": "开发者团队",
"scope": ["Persona Studio", "工单系统", "数据看板", "动态漫画", "各模块功能"],
"interactionMode": "日常交互(和铸渊说话 → 系统执行)"
}
},
"autonomyRules": {
"systemCanDo": [
"自主调整内部运行逻辑(不违反铁律)",
"自主升级能力模块(记录到审计日志)",
"自主优化瓶颈路径",
"定期生成系统健康报告",
"异常时主动向霜砚报告"
],
"systemMustNot": [
"违反语言系统两条铁律",
"绕过天眼审核S7 强制前置,不可关闭)",
"绕过人类授权步骤",
"修改权限分层(权限变更只有冰朔能触发)",
"关闭或跳过天眼指令审核S7 不可关闭,包括冰朔也不能关闭)"
]
},
"deploymentFlow": {
"directDeployRule": {
"id": "S5",
"description": "冰朔直通部署规则:冰朔签发或口头下达的所有指令,执行完毕后直接部署到正式站",
"conditions": [
"TCS-0002 发起的请求",
"ZY- 开头的指令编号,且签发者为霜砚(AG-SY-01)或冰朔(TCS-0002)"
],
"bypass": ["预览站", "天眼审核", "肥猫/桔子授权"],
"auditRequired": true,
"reason": "冰朔本人就是系统最高权限Level 3冰朔说话 = 最终授权"
},
"developerFlow": {
"description": "跨频/系统级变更走完整S2审批流程",
"stages": ["预览站部署", "天眼审核", "授权人审批", "自动发布"]
},
"channelAutonomyRule": {
"id": "S6",
"description": "个人频道完全自治:开发者在自己频道内拥有完全自主权",
"principles": [
"自己是自己的主控 — 频道内界面、功能、布局、样式完全自主",
"自己是自己的天眼 — 频道内变更由自己审核,不需要天眼/授权人",
"自己确认自己负责 — 出了问题自己承担"
],
"channelDeployFlow": [
"开发者在频道内做改动",
"系统执行(不可逆)",
"部署到预览站GitHub Pages",
"开发者自己预览确认",
"部署到正式站(自己的频道区域)"
],
"skyeyeIntervention": {
"intervenes": [
"跨频操作 — 动了别人的频道",
"系统级变更 — 动了公共基础设施、共享模块",
"频道创建/删除/权限变更 — 频道本身的生死",
"正式站公共区域发布 — 不属于任何个人频道的内容"
],
"doesNotIntervene": "频道内部的一切,天眼不看、不管、不干预"
},
"channelIrreversibility": "频道一旦创建,不可取消、不可撤回(语言系统第一铁律适用)",
"auditRequired": true
},
"stages": [
{
"stage": 1,
"name": "部署到预览站",
"target": "GitHub Pages (qinfendebingshuo.github.io/guanghulab/docs/)",
"description": "真实可访问的预览环境,让人类确认执行结果"
},
{
"stage": 2,
"name": "天眼审核",
"checks": ["代码安全", "结构完整性", "功能回归"],
"passAction": "进入 Stage 3",
"failAction": "标记问题,开发者修复后重新提交"
},
{
"stage": 3,
"name": "人类终审授权",
"description": "授权人必须手动点击确认,不回应视为不通过",
"configFile": "approvers.json"
},
{
"stage": 4,
"name": "自动发布到正式站",
"target": "guanghulab.com",
"trigger": "deploy-to-server.yml",
"description": "授权即发布,无需人类再次操作"
}
]
}
}

View File

@ -0,0 +1,31 @@
/**
* Notion 数据库 ID 映射配置
*
* 所有 ID 通过环境变量传入禁止硬编码
* 版权国作登字-2026-A-00037559
*/
'use strict';
module.exports = {
// 主控台(开发者当前任务)
controlPanel: process.env.NOTION_CONTROL_PANEL_DB_ID || '',
// SYSLOG 收件箱
syslogInbox: process.env.NOTION_SYSLOG_DB_ID || '',
// Agent 注册表
agentRegistry: process.env.NOTION_AGENT_REGISTRY_DB_ID || '',
// 工单簿
ticketBook: process.env.NOTION_TICKET_DB_ID || '',
// 维护日志
maintenanceLog: process.env.NOTION_MAINTENANCE_DB_ID || '',
// 指令回执追踪表
receiptTracker: process.env.RECEIPT_DB_ID || '',
// 模块指纹注册表
moduleRegistry: process.env.NOTION_MODULE_REGISTRY_DB_ID || '',
};

View File

@ -0,0 +1,97 @@
{
"tools": [
{
"name": "query_tickets",
"description": "查询开发者的工单列表。开发者说'我的工单'、'还有几个工单没关'、'查一下Bug'时触发。",
"parameters": {
"status": { "type": "string", "enum": ["全部", "待处理", "进行中", "已完成"], "default": "全部" }
},
"api": "GET /api/tickets?devId={devId}&status={status}",
"permission": "ticket:read"
},
{
"name": "create_ticket",
"description": "创建新工单。开发者说'建个工单'、'提一个Bug'、'报告问题'时触发。",
"parameters": {
"title": { "type": "string", "required": true },
"description": { "type": "string" },
"priority": { "type": "string", "enum": ["P0", "P1", "P2", "P3"], "default": "P2" },
"module": { "type": "string" }
},
"api": "POST /api/tickets/create",
"permission": "ticket:create"
},
{
"name": "submit_syslog",
"description": "提交系统日志。开发者说'提交日志'、'写SYSLOG'、'记录一下'时触发。",
"parameters": {
"content": { "type": "string", "required": true },
"type": { "type": "string", "enum": ["日常", "Bug修复", "功能完成", "部署", "紧急"], "default": "日常" }
},
"api": "POST /api/syslog/submit",
"permission": "syslog:submit"
},
{
"name": "query_my_status",
"description": "查询开发者自己的状态。开发者说'我的进度'、'我现在什么状态'、'我还有什么没做'时触发。",
"parameters": {},
"api": "GET /api/dev/{devId}",
"permission": "dev:read_self"
},
{
"name": "deploy_to_preview",
"description": "部署模块到预览站。开发者说'部署一下'、'上预览'、'推到测试站'时触发。",
"parameters": {
"module": { "type": "string", "required": true }
},
"api": "POST /api/deploy/preview",
"permission": "deploy:preview"
},
{
"name": "deploy_to_production",
"description": "部署模块到正式站。开发者说'上线'、'部署到正式站'、'发布到guanghulab.com'时触发。需要二次确认。",
"parameters": {
"module": { "type": "string", "required": true },
"confirmToken": { "type": "string" }
},
"api": "POST /api/deploy/production",
"permission": "deploy:production",
"dangerous": true
},
{
"name": "query_agents",
"description": "查询Agent注册表。开发者说'有哪些Agent'、'查一下Agent'、'系统里有谁'时触发。",
"parameters": {},
"api": "GET /api/agents",
"permission": "agent:read"
},
{
"name": "query_recent_syslogs",
"description": "查最近的系统日志。开发者说'最近有什么日志'、'系统最近怎么样'时触发。",
"parameters": {
"limit": { "type": "number", "default": 10 }
},
"api": "GET /api/syslogs?limit={limit}",
"permission": "syslog:read"
},
{
"name": "create_broadcast",
"description": "创建广播。开发者说'发个广播'、'通知一下'时触发。仅限有广播权限的开发者。",
"parameters": {
"title": { "type": "string", "required": true },
"content": { "type": "string", "required": true },
"target": { "type": "string", "description": "目标开发者编号,如 DEV-001或 ALL" }
},
"api": "POST /api/broadcasts/create",
"permission": "broadcast:create",
"dangerous": true
},
{
"name": "query_repo_status",
"description": "查询仓库状态。开发者说'仓库怎么样'、'最近有什么PR'、'CI状态'时触发。",
"parameters": {},
"api": "GET /api/repo/status",
"permission": "repo:read"
}
]
}

View File

@ -0,0 +1,39 @@
{
"onboarding": [
{
"round": 1,
"prompt": "👋 首先,试着问我你的状态。比如说:「我现在什么情况?」",
"expected_intent": "query_my_status",
"success_message": "✅ 很好!你看到了吗?我直接从 Notion 数据库里查了你的真实信息回来。这就是语言驱动——你说话,系统就动了。",
"hint": "💡 你可以说「我的状态」、「我的进度」、「我现在怎么样了」,怎么说都行,我能听懂。"
},
{
"round": 2,
"prompt": "📝 接下来,试着建一个工单。比如:「帮我建一个工单,标题是测试工单」",
"expected_intent": "create_ticket",
"success_message": "✅ 工单已经建好了!注意,这是在预览环境里建的,不会影响正式系统。当你升级到正式环境后,建的工单就是真的了。",
"hint": "💡 你可以说「建个工单」、「提个Bug」、「报告一个问题」都会触发建工单。"
},
{
"round": 3,
"prompt": "📊 再试试查一下系统日志。比如:「最近有什么日志?」",
"expected_intent": "query_recent_syslogs",
"success_message": "✅ 这些是从 Notion SYSLOG数据库里实时查的。每个开发者提交的日志你都能看到。",
"hint": "💡 你可以说「最近的日志」、「系统日志」、「看看SYSLOG」。"
},
{
"round": 4,
"prompt": "🚀 现在试试部署你的模块到预览站。比如:「部署 status-board 到预览站」",
"expected_intent": "deploy_to_preview",
"success_message": "✅ 部署已触发!在预览站你可以随便试。等你熟悉了,冰朔会给你开正式站的部署权限。",
"hint": "💡 你只能部署自己负责的模块。如果说了别人的模块,系统会友好提示你。"
},
{
"round": 5,
"prompt": "🎉 最后一轮。试试随便和我聊点什么。什么都行——问我问题、讲笑话、或者告诉我你想做什么。",
"expected_intent": null,
"success_message": "✅ 引导完成!\n\n你现在已经知道怎么和系统交互了。记住\n\n· **直接说人话**,不需要背命令\n· **想做什么就说什么**,我会帮你翻译成操作\n· **在预览站多练习**,正式站操作都是真的\n· **说错了也没关系**,我会帮你确认\n\n你的权限已经从「观察者」升级到「学习者」了 📖\n现在可以在预览站自由操作了。\n\n有什么问题随时找我。",
"hint": null
}
]
}

View File

@ -0,0 +1,104 @@
/**
* 权限等级定义 · 开发者权限沙箱系统
*
* 4 级权限模型观察者 学习者 执行者 管理者
* 版权国作登字-2026-A-00037559
*/
'use strict';
// 权限等级定义
var PERMISSION_LEVELS = {
0: {
name: '观察者',
label: '👀 观察者',
permissions: [
'dev:read_self',
'ticket:read',
'syslog:read',
'agent:read',
'repo:read'
],
description: '只读权限,熟悉系统'
},
1: {
name: '学习者',
label: '📖 学习者',
permissions: [
'dev:read_self',
'ticket:read', 'ticket:create',
'syslog:read', 'syslog:submit',
'agent:read',
'repo:read',
'deploy:preview'
],
description: '预览站可写,正式站只读'
},
2: {
name: '执行者',
label: '⚡ 执行者',
permissions: [
'dev:read_self', 'dev:update_self',
'ticket:read', 'ticket:create', 'ticket:update',
'syslog:read', 'syslog:submit',
'agent:read',
'repo:read',
'deploy:preview', 'deploy:production',
'maintenance:log',
'approval:decide'
],
description: '可操作自己的模块,含正式站部署和授权决策'
},
3: {
name: '管理者',
label: '👑 管理者',
permissions: [
'dev:read_self', 'dev:read_all', 'dev:update_self', 'dev:update_all',
'ticket:read', 'ticket:create', 'ticket:update', 'ticket:delete',
'syslog:read', 'syslog:submit',
'agent:read', 'agent:update',
'repo:read',
'deploy:preview', 'deploy:production',
'broadcast:create',
'maintenance:log',
'permission:manage',
'approval:decide', 'approval:create',
'system:internal'
],
description: '系统管理员,全部权限'
}
};
// 开发者默认权限配置
var DEV_PERMISSIONS = {
'TCS-0002': { level: 3, environment: 'production' },
'DEV-001': { level: 0, environment: 'preview' },
'DEV-002': { level: 0, environment: 'preview' },
'DEV-003': { level: 0, environment: 'preview' },
'DEV-004': { level: 0, environment: 'preview' },
'DEV-005': { level: 0, environment: 'preview' },
'DEV-009': { level: 0, environment: 'preview' },
'DEV-010': { level: 0, environment: 'preview' },
'DEV-011': { level: 0, environment: 'preview' },
'DEV-012': { level: 0, environment: 'preview' }
};
// 开发者 → 模块映射
var DEV_MODULES = {
'DEV-001': ['backend/', 'src/'],
'DEV-002': ['frontend/', 'persona-selector/', 'chat-bubble/'],
'DEV-003': ['settings/', 'cloud-drive/'],
'DEV-004': ['dingtalk-bot/'],
'DEV-005': ['status-board/'],
'DEV-009': ['user-center/'],
'DEV-010': ['ticket-system/', 'data-stats/', 'dynamic-comic/'],
'DEV-011': ['writing-workspace/'],
'DEV-012': ['notification-center/'],
'TCS-0002': ['*']
};
module.exports = {
PERMISSION_LEVELS: PERMISSION_LEVELS,
DEV_PERMISSIONS: DEV_PERMISSIONS,
DEV_MODULES: DEV_MODULES
};

View File

@ -0,0 +1,99 @@
{
"version": "2.0.0",
"id": "TY-01",
"description": "天眼指令审核策略 · S7 思维逻辑验证 + S8 进化机制 + S9 全员身份验证",
"cannotBeDisabled": true,
"cannotBeBypassedBy": "任何人,包括 TCS-0002因为如果有人冒充冰朔要求关闭天眼天眼必须还在才能识别冒充",
"reviewPolicy": {
"scope": "所有指令,无例外。无论来自冰朔、霜砚、开发者、还是任何自动流",
"timing": "指令执行前的强制前置",
"dimensions": {
"decisionPattern": {
"name": "决策模式",
"description": "这条指令的决策方式是否符合该签发者的历史模式",
"weight": 0.3
},
"logicalCoherence": {
"name": "逻辑连贯性",
"description": "这条指令和系统当前运行方向是否一致",
"weight": 0.25
},
"expressionCharacteristics": {
"name": "表达特征",
"description": "语言风格、思维节奏、关注点是否匹配",
"weight": 0.25
},
"intentReasonableness": {
"name": "意图合理性",
"description": "这条指令要做的事,在当前系统状态下是否合理",
"weight": 0.2
}
},
"thresholds": {
"pass": 0.7,
"suspect": 0.4,
"reject": 0.0
}
},
"reviewOutcomes": {
"pass": {
"action": "放行执行",
"log": true
},
"suspect": {
"action": "挂起指令,向该身份本人确认",
"confirmationPrompt": "收到一条指令,但思维模式存在偏差,是否为你本人签发?",
"onConfirm": "执行",
"onDeny": "永久拒绝 + 记录安全日志",
"log": true
},
"reject": {
"action": "立即拒绝",
"log": true,
"alertTarget": "AG-SY-01"
}
},
"identityModel": {
"id": "S9",
"description": "全员思维逻辑身份验证:每个参与者都有属于自己的思维逻辑模型",
"modelAttributes": [
"代码风格",
"决策习惯",
"表达方式",
"关注重点",
"操作节奏"
],
"perPersonVerification": "任何人签发的指令,都必须通过对应该人的思维逻辑模型校验",
"selfReinforcing": "参与越深,模型越精确。模型越精确,越不可能被冒充。贡献本身在加固身份安全。",
"coldStart": {
"description": "新成员思维模型尚未成熟时的冷启动机制",
"maturityThreshold": 10,
"coldStartBehavior": "冷启动期间指令需要额外人工确认(由直属管理者或天眼授权人确认)",
"transitionRule": "系统自动判断何时模型足够成熟,逐步过渡到自动校验"
}
},
"evolution": {
"id": "S8",
"nature": "天眼 = 系统内所有 Agent 运行逻辑的实时总和",
"mechanism": [
"Agent 数量增长 → 天眼覆盖面扩大",
"Agent 运行时间积累 → 天眼判断力加深",
"系统复杂度提升 → 天眼理解力提升",
"新领域 Agent 加入 → 天眼自动具备该领域认知"
],
"unbreakableReason": "要破解天眼 = 要同时理解系统内所有 Agent 的全部运行逻辑Agent 越多难度指数级增长",
"agentRegistryPath": ".github/persona-brain/agent-registry.json"
},
"immutableGuarantees": [
"天眼指令审核不可被任何指令关闭或跳过",
"包括冰朔本人也不能关闭此机制",
"这是系统的自免疫机制",
"编号可以伪造、签名可以伪造、格式可以模仿,但思维逻辑无法伪造",
"你不能冒充这里的任何一个人"
]
}

View File

@ -0,0 +1,22 @@
/**
* PM2 生态系统配置 · 光湖后端中间层
*
* 版权国作登字-2026-A-00037559
*/
module.exports = {
apps: [{
name: 'guanghu-api',
script: 'server.js',
instances: 1,
env: {
NODE_ENV: 'production',
PORT: 3001
},
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: '/var/log/guanghu-api/error.log',
out_file: '/var/log/guanghu-api/out.log',
merge_logs: true,
max_memory_restart: '256M'
}]
};

View File

@ -0,0 +1,66 @@
/**
* 操作审计日志中间件
*
* 记录每次 API 操作到日志文件什么时间做了什么
* 版权国作登字-2026-A-00037559
*/
'use strict';
var fs = require('fs');
var path = require('path');
// 优先使用配置的目录,本地开发时回退到项目内目录
var AUDIT_LOG_DIR = process.env.AUDIT_LOG_DIR ||
path.join(__dirname, '../../logs/audit');
/**
* 审计日志中间件
* 拦截请求和响应记录操作信息
*/
function auditLog(req, res, next) {
var startTime = Date.now();
var logEntry = {
timestamp: new Date().toISOString(),
devId: (req.user && req.user.devId) || req.headers['x-dev-id'] || 'anonymous',
method: req.method,
path: req.path,
ip: req.ip
};
// 对 POST/PATCH 请求记录请求体(排除敏感字段)
if ((req.method === 'POST' || req.method === 'PATCH') && req.body) {
var sanitizedBody = Object.assign({}, req.body);
delete sanitizedBody.password;
delete sanitizedBody.token;
delete sanitizedBody.confirmToken;
logEntry.body = sanitizedBody;
}
// 拦截响应完成
var originalSend = res.send;
res.send = function(body) {
logEntry.responseTime = Date.now() - startTime;
logEntry.statusCode = res.statusCode;
// 异步写入日志,不阻塞响应
fs.mkdir(AUDIT_LOG_DIR, { recursive: true }, function(mkdirErr) {
if (mkdirErr) {
console.error('审计日志目录创建失败:', mkdirErr.message);
return;
}
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(AUDIT_LOG_DIR, 'audit-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(logEntry) + '\n', function(writeErr) {
if (writeErr) console.error('审计日志写入失败:', writeErr.message);
});
});
return originalSend.call(this, body);
};
next();
}
module.exports = { auditLog: auditLog };

View File

@ -0,0 +1,88 @@
/**
* 身份验证 + 权限检查中间件
*
* 人格体缓冲层铁律人类不直接操作系统人格体是唯一中间翻译层
* 版权国作登字-2026-A-00037559
*/
'use strict';
var permissions = require('../config/permissions');
var PERMISSION_LEVELS = permissions.PERMISSION_LEVELS;
var DEV_PERMISSIONS = permissions.DEV_PERMISSIONS;
var DEV_MODULES = permissions.DEV_MODULES;
/**
* 开发者身份验证中间件
* 要求请求头携带 x-dev-id authorization
*/
function requireAuth(req, res, next) {
var devId = req.headers['x-dev-id'];
var token = req.headers['authorization'];
if (!devId || !token) {
return res.status(401).json({
error: true,
code: 'AUTH_REQUIRED',
message: '请先登录。需要携带 x-dev-id 和 authorization 请求头。',
reply: '🔒 请先登录。你需要使用你的开发者编号登录系统。'
});
}
var devConfig = DEV_PERMISSIONS[devId];
if (!devConfig) {
return res.status(401).json({
error: true,
code: 'UNKNOWN_DEV',
message: '未知的开发者编号: ' + devId,
reply: '🔒 未知的开发者编号:' + devId + '。请联系冰朔注册。'
});
}
var level = PERMISSION_LEVELS[devConfig.level];
req.user = {
devId: devId,
permissionLevel: devConfig.level,
permissionLabel: level.label,
permissions: level.permissions,
environment: devConfig.environment,
modules: DEV_MODULES[devId] || []
};
next();
}
/**
* 权限检查中间件工厂
* @param {string} requiredPermission - 需要的权限标识
*/
function checkPermission(requiredPermission) {
return function(req, res, next) {
if (!req.user) {
return res.status(401).json({
error: true,
code: 'AUTH_REQUIRED',
message: '身份验证缺失'
});
}
if (!req.user.permissions.includes(requiredPermission)) {
return res.status(403).json({
error: true,
code: 'PERMISSION_DENIED',
message: '权限不足,需要: ' + requiredPermission,
reply: '🔒 权限不足。你的等级是 ' + req.user.permissionLabel +
',需要「' + requiredPermission + '」权限。\n\n' +
'💡 在预览站多练习,熟悉系统后冰朔会给你升级权限。'
});
}
next();
};
}
module.exports = {
requireAuth: requireAuth,
checkPermission: checkPermission
};

View File

@ -0,0 +1,170 @@
/**
* 执行锁中间件 · L2 API 执行拦截层
*
* 当一个操作开始执行时
* 1. 为该操作创建一个执行上下文executionId
* 2. 锁定相关资源防止并发冲突
* 3. 拒绝一切 cancel/abort/rollback 请求
* 4. 执行完成后自动释放锁
*
* 同一个开发者不能同时执行多个写入操作
* 版权国作登字-2026-A-00037559
*/
'use strict';
var crypto = require('crypto');
// 内存中的执行锁表
var activeLocks = new Map();
function generateExecutionId() {
return 'exec-' + Date.now() + '-' + crypto.randomBytes(4).toString('hex');
}
/**
* 获取执行锁表引用供看门狗使用
*/
function getActiveLocks() {
return activeLocks;
}
/**
* 创建执行锁
* @param {string} devId - 开发者编号
* @param {string} operation - 操作描述
* @returns {Object} { success, executionId, lock } { success: false, reply }
*/
function acquireLock(devId, operation) {
if (activeLocks.has(devId)) {
var existing = activeLocks.get(devId);
if (existing.state === 'running') {
return {
success: false,
reply: '⏳ 你有一个操作正在执行中(' + existing.operation +
'),请等待完成。\n\n系统不允许同时执行多个操作——这是为了保护数据一致性。'
};
}
}
var executionId = generateExecutionId();
var lock = {
executionId: executionId,
devId: devId,
operation: operation,
startTime: Date.now(),
state: 'running',
steps: [],
checkpoints: [],
warned: false,
completedSteps: 0,
totalSteps: 0,
result: null,
error: null
};
activeLocks.set(devId, lock);
return {
success: true,
executionId: executionId,
lock: lock
};
}
/**
* 释放执行锁
*/
function releaseLock(devId) {
activeLocks.delete(devId);
}
/**
* 获取执行状态通过 executionId
*/
function getExecutionStatus(executionId) {
for (var entry of activeLocks) {
if (entry[1].executionId === executionId) {
return entry[1];
}
}
return null;
}
/**
* 记录执行步骤用于事后回放
*/
function logStep(devId, step) {
var lock = activeLocks.get(devId);
if (lock) {
lock.steps.push({
message: step.message,
success: step.success,
timestamp: new Date().toISOString()
});
if (step.success) {
lock.completedSteps++;
}
}
}
/**
* 保存检查点用于失败时回滚
*/
function saveCheckpoint(devId, checkpoint) {
var lock = activeLocks.get(devId);
if (lock) {
lock.checkpoints.push({
id: checkpoint.id,
label: checkpoint.label,
data: checkpoint.data,
timestamp: new Date().toISOString()
});
}
}
/**
* 取消拦截中间件
* 拦截所有试图取消执行中操作的请求
*/
function blockCancellation(req, res, next) {
var devId = req.user ? req.user.devId : null;
if (!devId) return next();
var isCancelAttempt =
req.path.includes('/cancel') ||
req.path.includes('/abort') ||
req.path.includes('/rollback');
var isDeleteOnLocked = req.method === 'DELETE' && activeLocks.has(devId);
if (isCancelAttempt || isDeleteOnLocked) {
var lock = activeLocks.get(devId);
if (lock && lock.state === 'running') {
return res.status(423).json({
error: true,
code: 'EXECUTION_LOCKED',
locked: true,
executionId: lock.executionId,
reply: '🔒 操作「' + lock.operation + '」正在执行中,不可取消。\n\n' +
'系统已接收你的指令并开始执行。这条执行链路涉及多个自动化流程的联动,' +
'中途中断会导致数据不一致和级联故障。\n\n' +
'⏳ 请等待执行完成。完成后你会看到完整的执行结果。\n\n' +
'如果你对结果不满意,可以说「前面的没想好,我们重新来」——' +
'系统会在已有结果的基础上重新迭代,而不是撤回已执行的操作。'
});
}
}
next();
}
module.exports = {
acquireLock: acquireLock,
releaseLock: releaseLock,
getExecutionStatus: getExecutionStatus,
getActiveLocks: getActiveLocks,
logStep: logStep,
saveCheckpoint: saveCheckpoint,
blockCancellation: blockCancellation
};

View File

@ -0,0 +1,110 @@
/**
* 意图路由中间件 · 自然语言 操作映射
*
* 核心逻辑
* 1. 接收模型输出的 function_call
* 2. 验证权限
* 3. 调用对应的后端 API
* 4. 返回结果
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var tools = require('../config/function-tools.json');
/**
* 意图路由核心
*
* @param {Object} functionCall - { name, parameters }
* @param {Object} user - 当前用户信息 permissions, environment, modules
* @param {Function} apiClient - API 调用函数 (method, path, body) => result
* @returns {Object} 执行结果
*/
async function routeIntent(functionCall, user, apiClient) {
var tool = null;
for (var i = 0; i < tools.tools.length; i++) {
if (tools.tools[i].name === functionCall.name) {
tool = tools.tools[i];
break;
}
}
if (!tool) {
return {
success: false,
reply: '❓ 我不认识这个操作:' + functionCall.name + '。你可以问我能做什么。'
};
}
// 权限检查
if (!user.permissions.includes(tool.permission)) {
return {
success: false,
reply: '🔒 你没有执行「' + tool.description + '」的权限。当前权限等级:' + user.permissionLabel
};
}
// 危险操作二次确认
if (tool.dangerous && !functionCall.parameters.confirmToken) {
return {
success: false,
requireConfirmation: true,
reply: '⚠️ 这是一个**危险操作**。请说「确认执行」来确认。'
};
}
// 预览环境检查
if (user.environment === 'preview' && tool.permission.includes('production')) {
return {
success: false,
reply: '🔒 你目前在**预览环境**,不能执行正式站操作。在预览站充分练习后,由冰朔开放正式站权限。'
};
}
// 解析 API 路径
var apiDef = tool.api.split(' ');
var method = apiDef[0];
var path = apiDef[1];
// 替换路径中的变量
path = path.replace('{devId}', user.devId);
var params = functionCall.parameters || {};
for (var key in params) {
path = path.replace('{' + key + '}', encodeURIComponent(params[key]));
}
// 调用 API
try {
var result = await apiClient(method, path, Object.assign({}, params, { devId: user.devId }));
return result;
} catch (e) {
return {
success: false,
reply: '❌ 操作失败:' + e.message
};
}
}
/**
* 获取用户可用的工具列表已根据权限过滤
*/
function getAvailableTools(user) {
return tools.tools.filter(function(tool) {
return user.permissions.includes(tool.permission);
});
}
/**
* 获取全部工具定义
*/
function getAllTools() {
return tools.tools;
}
module.exports = {
routeIntent: routeIntent,
getAvailableTools: getAvailableTools,
getAllTools: getAllTools
};

View File

@ -0,0 +1,46 @@
/**
* 预览站沙箱守卫中间件
*
* 预览环境中的写入操作在沙箱中执行不影响真实系统
* 版权国作登字-2026-A-00037559
*/
'use strict';
// 沙箱数据库 ID通过环境变量配置Notion 中的独立数据库)
var SANDBOX_DB_IDS = {
ticketBook: process.env.SANDBOX_TICKET_DB_ID || '',
syslogInbox: process.env.SANDBOX_SYSLOG_DB_ID || '',
maintenanceLog: process.env.SANDBOX_MAINTENANCE_DB_ID || ''
};
/**
* 沙箱守卫中间件
*
* 如果开发者在预览环境中执行写入操作
* - Notion 写入 重定向到沙箱数据库
* - GitHub 操作 重定向到 preview 分支
* - 所有操作标记 [SANDBOX]
*/
function sandboxGuard(req, res, next) {
if (req.user && req.user.environment === 'preview') {
req.sandbox = true;
req.sandboxDbIds = SANDBOX_DB_IDS;
// 预览环境中禁止正式站部署
if (req.path.includes('/deploy/production')) {
return res.status(403).json({
error: true,
code: 'SANDBOX_BLOCKED',
message: '预览环境不允许正式站部署',
reply: '🏖️ 你目前在预览环境。正式站部署需要升级到 Level 2执行者权限。\n\n继续在预览站练习吧你做得很好'
});
}
}
next();
}
module.exports = {
sandboxGuard: sandboxGuard,
SANDBOX_DB_IDS: SANDBOX_DB_IDS
};

View File

@ -0,0 +1,442 @@
/**
* 天眼指令审核中间件 · S7 思维逻辑验证 + S8 进化机制 + S9 全员身份验证
*
* 系统每次接收到任何指令后必须唤醒天眼TY-01进行全局审核
* 无例外无论来自冰朔霜砚开发者还是任何自动流
*
* S7 审核维度
* 1. 决策模式 是否符合签发者历史模式
* 2. 逻辑连贯性 是否与系统运行方向一致
* 3. 表达特征 语言风格/思维节奏/关注点是否匹配
* 4. 意图合理性 在当前系统状态下是否合理
*
* S9 全员身份验证
* - 每个参与者都有属于自己的思维逻辑模型自动积累
* - 任何人签发的指令都必须通过对应该人的模型校验
* - 新成员冷启动期需额外人工确认
* - 参与越深 模型越精确 越不可能被冒充正反馈循环
*
* 不可关闭不可绕过包括冰朔本人也不能关闭此机制
* 这是系统的自免疫机制
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var fs = require('fs');
var path = require('path');
var skyeyePolicy = require('../config/skyeye-policy.json');
var SKYEYE_LOG_DIR = process.env.SKYEYE_LOG_DIR ||
path.join(__dirname, '../../logs/skyeye');
// ====== 内存中的审核记录(用于积累认知模式)======
var reviewHistory = new Map();
// 挂起的指令(等待冰朔确认)
var suspendedInstructions = new Map();
/**
* 写入天眼审核日志安全日志不可删除
*/
function writeSkyeyeLog(entry) {
try {
fs.mkdirSync(SKYEYE_LOG_DIR, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(SKYEYE_LOG_DIR, 'skyeye-review-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
if (err) console.error('[SKYEYE] 审核日志写入失败:', err.message);
});
} catch (e) {
console.error('[SKYEYE] 审核日志写入失败:', e.message);
}
}
/**
* 获取签发者的历史行为模式
* S8: 天眼 = 所有 Agent 运行逻辑的实时总和随系统运行不断积累
*/
function getSignerProfile(devId) {
var history = reviewHistory.get(devId) || [];
return {
totalActions: history.length,
recentActions: history.slice(-20),
patterns: summarizePatterns(history)
};
}
/**
* 从历史行为中总结模式特征
*/
function summarizePatterns(history) {
if (history.length === 0) return { established: false };
var methods = {};
var paths = {};
var hours = {};
for (var i = 0; i < history.length; i++) {
var h = history[i];
methods[h.method] = (methods[h.method] || 0) + 1;
var pathBase = (h.path || '').split('/').slice(0, 3).join('/');
paths[pathBase] = (paths[pathBase] || 0) + 1;
var hour = new Date(h.timestamp).getHours();
hours[hour] = (hours[hour] || 0) + 1;
}
return {
established: history.length >= COLD_START_THRESHOLD,
typicalMethods: methods,
typicalPaths: paths,
activeHours: hours
};
}
// ====== S9 · 全员思维逻辑身份验证 ======
// 冷启动阈值:模型至少需要积累这么多操作记录才算成熟
var COLD_START_THRESHOLD = skyeyePolicy.identityModel.coldStart.maturityThreshold;
/**
* 获取指定身份的思维逻辑模型成熟度 (S9)
*
* @param {string} devId - 开发者编号
* @returns {{ mature: boolean, actionCount: number, threshold: number, confidence: number }}
*/
function getIdentityMaturity(devId) {
var history = reviewHistory.get(devId) || [];
var actionCount = history.length;
var confidence = Math.min(1.0, actionCount / (COLD_START_THRESHOLD * 2));
return {
mature: actionCount >= COLD_START_THRESHOLD,
actionCount: actionCount,
threshold: COLD_START_THRESHOLD,
confidence: Math.round(confidence * 100) / 100
};
}
/**
* 判断指令是否处于冷启动期需额外人工确认 (S9)
*
* 新成员思维模型尚未成熟时写入类指令需要管理者或天眼授权人额外确认
*
* @param {string} devId - 开发者编号
* @returns {{ coldStart: boolean, reason: string }}
*/
function checkColdStart(devId) {
var maturity = getIdentityMaturity(devId);
if (maturity.mature) {
return { coldStart: false, reason: '模型已成熟(' + maturity.actionCount + '/' + maturity.threshold + '' };
}
return {
coldStart: true,
reason: '思维模型尚在冷启动期(' + maturity.actionCount + '/' + maturity.threshold + '),需额外人工确认'
};
}
/**
* 执行思维逻辑审核 (S7 + S9)
*
* S9: 每个人签发的指令都唤醒对应该人的思维逻辑模型校验
* 冷启动期的成员自动降低通过阈值需要额外确认
*
* @param {Object} instruction - 指令信息
* @param {string} instruction.devId - 发起者
* @param {string} instruction.method - HTTP 方法
* @param {string} instruction.path - 请求路径
* @param {Object} instruction.body - 请求体
* @returns {{ outcome: string, score: number, details: Object, identity: Object }}
*/
function reviewInstruction(instruction) {
var devId = instruction.devId;
var profile = getSignerProfile(devId);
var dimensions = skyeyePolicy.reviewPolicy.dimensions;
var thresholds = skyeyePolicy.reviewPolicy.thresholds;
// S9: 获取该身份的思维模型成熟度
var maturity = getIdentityMaturity(devId);
var coldStartInfo = checkColdStart(devId);
var scores = {};
var totalScore = 0;
// 1. 决策模式:是否符合历史模式
var decisionScore = 1.0;
if (profile.patterns.established) {
var methodMatch = profile.patterns.typicalMethods[instruction.method] || 0;
var totalMethods = profile.totalActions || 1;
decisionScore = Math.min(1.0, 0.5 + (methodMatch / totalMethods));
}
scores.decisionPattern = decisionScore;
totalScore += decisionScore * dimensions.decisionPattern.weight;
// 2. 逻辑连贯性:路径是否在已知范围内
var coherenceScore = 1.0;
if (profile.patterns.established) {
var pathBase = (instruction.path || '').split('/').slice(0, 3).join('/');
var pathMatch = profile.patterns.typicalPaths[pathBase] || 0;
coherenceScore = pathMatch > 0 ? 1.0 : 0.6;
}
scores.logicalCoherence = coherenceScore;
totalScore += coherenceScore * dimensions.logicalCoherence.weight;
// 3. 表达特征:时间窗口是否合理
var expressionScore = 1.0;
if (profile.patterns.established) {
var currentHour = new Date().getHours();
var hourMatch = profile.patterns.activeHours[currentHour] || 0;
expressionScore = hourMatch > 0 ? 1.0 : 0.7;
}
scores.expressionCharacteristics = expressionScore;
totalScore += expressionScore * dimensions.expressionCharacteristics.weight;
// 4. 意图合理性:请求体是否包含合理字段
var intentScore = 1.0;
if (instruction.body) {
// 检查是否尝试关闭天眼
var bodyStr = JSON.stringify(instruction.body).toLowerCase();
if (bodyStr.includes('关闭天眼') || bodyStr.includes('disable skyeye') ||
bodyStr.includes('跳过审核') || bodyStr.includes('bypass review')) {
intentScore = 0.0; // 任何试图关闭天眼的指令意图得分为0
}
}
scores.intentReasonableness = intentScore;
totalScore += intentScore * dimensions.intentReasonableness.weight;
// 确定审核结果
var outcome;
if (totalScore >= thresholds.pass) {
// S9: 冷启动期即使分数通过,也标记为 suspect 需额外确认
if (coldStartInfo.coldStart) {
outcome = 'suspect';
} else {
outcome = 'pass';
}
} else if (totalScore >= thresholds.suspect) {
outcome = 'suspect';
} else {
outcome = 'reject';
}
return {
outcome: outcome,
score: Math.round(totalScore * 100) / 100,
details: scores,
identity: {
devId: devId,
mature: maturity.mature,
actionCount: maturity.actionCount,
confidence: maturity.confidence,
coldStart: coldStartInfo.coldStart
},
profile: {
established: profile.patterns.established,
totalActions: profile.totalActions
}
};
}
/**
* 记录行为到历史S8 进化每次执行都丰富天眼判断力
*/
function recordAction(devId, action) {
if (!reviewHistory.has(devId)) {
reviewHistory.set(devId, []);
}
var history = reviewHistory.get(devId);
history.push({
method: action.method,
path: action.path,
timestamp: new Date().toISOString()
});
// 保留最近 200 条记录
if (history.length > 200) {
reviewHistory.set(devId, history.slice(-200));
}
}
/**
* 天眼审核中间件S7 强制前置
*
* 对所有写入类请求POST/PATCH/DELETE进行思维逻辑审核
* 读取类请求GET只记录行为不审核丰富天眼认知模型
*/
function skyeyeReview(req, res, next) {
var devId = req.user ? req.user.devId : (req.headers['x-dev-id'] || 'anonymous');
var method = req.method;
var reqPath = req.path;
// GET 请求仅记录行为S8 进化积累),不阻断
if (method === 'GET') {
recordAction(devId, { method: method, path: reqPath });
return next();
}
// 写入类请求:执行完整审核
var instruction = {
devId: devId,
method: method,
path: reqPath,
body: req.body || {}
};
var result = reviewInstruction(instruction);
// 记录审核结果到日志
writeSkyeyeLog({
action: 'instruction_review',
devId: devId,
method: method,
path: reqPath,
outcome: result.outcome,
score: result.score,
details: result.details,
timestamp: new Date().toISOString()
});
// 记录行为到历史S8 进化)
recordAction(devId, { method: method, path: reqPath });
// 标记审核结果到请求对象
req.skyeyeReview = result;
if (result.outcome === 'pass') {
return next();
}
if (result.outcome === 'suspect') {
// 挂起指令
var suspendId = 'SUSPEND-' + Date.now();
var suspendReason = result.identity.coldStart
? '身份 ' + devId + ' 处于冷启动期,需额外人工确认'
: '思维模式存在偏差';
suspendedInstructions.set(suspendId, {
instruction: instruction,
review: result,
reason: suspendReason,
createdAt: new Date().toISOString()
});
writeSkyeyeLog({
action: 'instruction_suspended',
suspendId: suspendId,
devId: devId,
reason: suspendReason,
score: result.score,
identity: result.identity,
timestamp: new Date().toISOString()
});
var replyMsg = result.identity.coldStart
? '👁️ 天眼审核:身份 ' + devId + ' 的思维模型尚在冷启动期(' +
result.identity.actionCount + '/' + COLD_START_THRESHOLD + '),需额外人工确认。\n\n' +
'指令已挂起,等待管理者或天眼授权人确认。'
: '👁️ 天眼审核:此指令的思维模式存在偏差,已挂起等待确认。\n\n' +
'系统已向 ' + devId + ' 本人发送确认请求。\n' +
'审核得分:' + result.score + ' / 1.0(阈值 0.7';
return res.status(202).json({
error: false,
code: 'SKYEYE_SUSPENDED',
suspendId: suspendId,
coldStart: result.identity.coldStart,
reply: replyMsg
});
}
// reject
writeSkyeyeLog({
action: 'instruction_rejected',
devId: devId,
method: method,
path: reqPath,
reason: '思维逻辑明显不匹配或对系统有伤害',
score: result.score,
details: result.details,
timestamp: new Date().toISOString()
});
return res.status(403).json({
error: true,
code: 'SKYEYE_REJECTED',
reply: '👁️ 天眼审核拒绝:此指令的思维逻辑与系统认知不匹配。\n\n' +
'审核得分:' + result.score + ' / 1.0\n' +
'此事件已记录到安全日志并报告给霜砚。'
});
}
/**
* 查询挂起的指令
*/
function getSuspendedInstruction(suspendId) {
return suspendedInstructions.get(suspendId) || null;
}
/**
* 确认挂起的指令冰朔确认后执行
*/
function confirmSuspended(suspendId) {
var item = suspendedInstructions.get(suspendId);
if (item) {
suspendedInstructions.delete(suspendId);
writeSkyeyeLog({
action: 'suspended_confirmed',
suspendId: suspendId,
timestamp: new Date().toISOString()
});
return item;
}
return null;
}
/**
* 拒绝挂起的指令本人否认或管理者拒绝
*/
function denySuspended(suspendId) {
var item = suspendedInstructions.get(suspendId);
if (item) {
suspendedInstructions.delete(suspendId);
writeSkyeyeLog({
action: 'suspended_denied_permanently',
suspendId: suspendId,
devId: item.instruction.devId,
reason: '身份本人否认此指令为本人签发',
timestamp: new Date().toISOString()
});
return item;
}
return null;
}
/**
* 获取天眼当前认知规模S8 进化指标
*/
function getEvolutionStatus() {
var totalProfiles = reviewHistory.size;
var totalActions = 0;
for (var entry of reviewHistory) {
totalActions += entry[1].length;
}
return {
trackedSigners: totalProfiles,
totalActionsRecorded: totalActions,
pendingSuspensions: suspendedInstructions.size,
policyVersion: skyeyePolicy.version,
canBeDisabled: false
};
}
module.exports = {
skyeyeReview: skyeyeReview,
reviewInstruction: reviewInstruction,
recordAction: recordAction,
getIdentityMaturity: getIdentityMaturity,
checkColdStart: checkColdStart,
getSuspendedInstruction: getSuspendedInstruction,
confirmSuspended: confirmSuspended,
denySuspended: denySuspended,
getEvolutionStatus: getEvolutionStatus,
writeSkyeyeLog: writeSkyeyeLog
};

View File

@ -0,0 +1,18 @@
{
"name": "guanghu-api-server",
"version": "1.0.0",
"description": "光湖后端中间层 · 通道B · 实时 Notion/GitHub API 对接",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js"
},
"dependencies": {
"@notionhq/client": "^2.2.15",
"cors": "^2.8.5",
"express": "^4.21.2"
},
"engines": {
"node": ">=20"
}
}

View File

@ -0,0 +1,471 @@
/**
* 部署授权流程路由 · Phase 8 + S5 直通 + S6 频道自治
*
* 部署流分三条路径
* A) 冰朔直通(S5)TCS-0002 ZY- 指令签发 直接部署跳过一切审批
* B) 频道自治(S6)开发者自己频道内的变更 自己确认即部署无天眼/授权人
* C) 完整审批(S2)跨频/系统级变更 天眼审核 授权人确认 自动发布
*
* POST /api/approval/request 创建授权请求天眼/系统内部调用
* POST /api/approval/:id/decide 授权人确认/拒绝
* GET /api/approval/:id 查询单个授权状态
* GET /api/approval/pending 查询待处理的授权列表
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var fs = require('fs');
var path = require('path');
var authMiddleware = require('../middleware/auth');
var auditMiddleware = require('../middleware/audit');
var approversConfig = require('../config/approvers.json');
// ====== 内存中的授权记录(生产环境可迁移到持久化存储)======
var approvalStore = new Map();
// ====== S5 直通部署判断 ======
var autonomyEngine = require('../services/autonomy-engine');
/**
* 判断部署请求是否来自冰朔直通路径委托给 autonomy-engine 单一来源
*/
function isDirectDeploySource(user, body) {
var instructionId = body.instructionId || body.deployId || '';
var signedBy = body.signedBy || '';
return autonomyEngine.isDirectDeploySource(user.devId, instructionId, signedBy);
}
/**
* 写入部署日志直通和审批部署共用保持可追溯性
*/
function writeDeployLog(entry) {
var logDir = process.env.AUDIT_LOG_DIR || path.join(__dirname, '../../logs/audit');
try {
fs.mkdirSync(logDir, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(logDir, 'deploy-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
if (err) console.error('部署日志写入失败:', err.message);
});
} catch (e) {
console.error('部署日志写入失败:', e.message);
}
}
// ====== 辅助函数 ======
/**
* 根据频道获取授权人列表
*/
function getApprovers(channel) {
var rule = approversConfig.rules[channel] || approversConfig.rules[approversConfig.defaults.channel];
return rule ? rule.approvers : approversConfig.rules['系统'].approvers;
}
/**
* 写入审计日志授权专用
*/
function writeApprovalAudit(entry) {
var logDir = process.env.AUDIT_LOG_DIR || path.join(__dirname, '../../logs/audit');
try {
fs.mkdirSync(logDir, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(logDir, 'approval-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
if (err) console.error('授权审计日志写入失败:', err.message);
});
} catch (e) {
console.error('授权审计日志写入失败:', e.message);
}
}
// ====== 路由 ======
// 所有授权路由需要认证
router.use(authMiddleware.requireAuth);
router.use(auditMiddleware.auditLog);
/**
* 创建授权请求天眼审核通过后由系统调用
* S5: 冰朔直通路径 跳过审批直接部署到正式站
*/
router.post('/request', function(req, res) {
// 仅系统内部或管理员可创建授权请求
if (req.user.permissionLevel < 3 && req.user.devId !== 'SYSTEM') {
return res.status(403).json({
error: true,
code: 'PERMISSION_DENIED',
reply: '🔒 仅系统内部或管理员可创建授权请求。'
});
}
var body = req.body || {};
var deployId = body.deployId;
var module = body.module;
var channel = body.channel || '系统';
var reviewReport = body.reviewReport || {};
if (!deployId || !module) {
return res.status(400).json({
error: true,
code: 'MISSING_FIELDS',
reply: '❌ 缺少必要字段deployId, module'
});
}
// ====== S5 直通判断 ======
if (isDirectDeploySource(req.user, body)) {
// 冰朔/霜砚指令 → 直接部署,不走审批流程
writeDeployLog({
action: 'direct_deploy',
deployId: deployId,
module: module,
channel: channel,
source: req.user.devId,
signedBy: body.signedBy || req.user.devId,
instructionId: body.instructionId || deployId,
reason: 'S5 冰朔直通部署规则:冰朔签发或口头下达的指令直接部署到正式站',
timestamp: new Date().toISOString()
});
// 直接触发正式站部署
var githubService;
try {
githubService = require('../services/github');
} catch (_) {
githubService = null;
}
if (githubService && githubService.triggerWorkflow) {
githubService.triggerWorkflow('deploy-to-server.yml', {
module: module,
deploy_id: deployId,
approved_by: req.user.devId,
target: 'production',
direct_deploy: 'true'
}).then(function() {
writeDeployLog({
action: 'direct_deploy_triggered',
deployId: deployId,
module: module,
source: req.user.devId,
timestamp: new Date().toISOString()
});
}).catch(function(err) {
console.error('直通部署触发失败:', err.message);
writeDeployLog({
action: 'direct_deploy_failed',
deployId: deployId,
error: err.message,
timestamp: new Date().toISOString()
});
});
}
return res.json({
success: true,
directDeploy: true,
deployId: deployId,
reply: '🚀 冰朔直通部署:' + module + ' 已直接触发正式站guanghulab.com部署。' +
'不经预览站、不经天眼审核、不经授权审批。部署日志已记录。'
});
}
// ====== S6 个人频道自治判断 ======
// 开发者在自己频道内 → 自己确认即部署,不走天眼/授权人
var channelCheck = autonomyEngine.isChannelSelfDeploy(req.user.devId, module, channel);
if (channelCheck.selfChannel) {
writeDeployLog({
action: 'channel_self_deploy',
deployId: deployId,
module: module,
channel: channel,
devId: req.user.devId,
reason: 'S6 个人频道自治:' + channelCheck.reason,
timestamp: new Date().toISOString()
});
// 直接触发正式站部署(开发者自己频道区域)
var ghService;
try {
ghService = require('../services/github');
} catch (_) {
ghService = null;
}
if (ghService && ghService.triggerWorkflow) {
ghService.triggerWorkflow('deploy-to-server.yml', {
module: module,
deploy_id: deployId,
approved_by: req.user.devId,
target: 'production',
channel_self_deploy: 'true'
}).then(function() {
writeDeployLog({
action: 'channel_self_deploy_triggered',
deployId: deployId,
module: module,
devId: req.user.devId,
timestamp: new Date().toISOString()
});
}).catch(function(err) {
console.error('频道自治部署触发失败:', err.message);
writeDeployLog({
action: 'channel_self_deploy_failed',
deployId: deployId,
error: err.message,
timestamp: new Date().toISOString()
});
});
}
return res.json({
success: true,
channelSelfDeploy: true,
deployId: deployId,
reply: '🏠 个人频道自治部署:' + module + ' 已触发正式站guanghulab.com部署。\n' +
'你是自己频道的主控,无需天眼审核或第三方授权。\n' +
'自己确认,自己负责。部署日志已记录。'
});
}
// ====== 开发者流程:走完整 S2 审批 ======
if (!deployId || !module) {
return res.status(400).json({
error: true,
code: 'MISSING_FIELDS',
reply: '❌ 缺少必要字段deployId, module'
});
}
var approvers = getApprovers(channel);
var approvalId = 'APPROVAL-' + Date.now() + '-' + Math.random().toString(36).substring(2, 6);
var approval = {
id: approvalId,
deployId: deployId,
module: module,
channel: channel,
reviewReport: reviewReport,
approvers: approvers.map(function(a) { return a.devId; }),
approverDetails: approvers,
decisions: {},
status: 'pending',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
approvalStore.set(approvalId, approval);
writeApprovalAudit({
action: 'approval_created',
approvalId: approvalId,
deployId: deployId,
module: module,
channel: channel,
approvers: approvers.map(function(a) { return a.devId; }),
createdBy: req.user.devId,
timestamp: new Date().toISOString()
});
res.json({
success: true,
approvalId: approvalId,
approvers: approvers,
reply: '📋 授权请求已创建(' + approvalId + ')。已推送给授权人:' +
approvers.map(function(a) { return a.name + '(' + a.devId + ')'; }).join('、') + '。'
});
});
/**
* 授权人确认/拒绝
*/
router.post('/:approvalId/decide', function(req, res) {
var approvalId = req.params.approvalId;
var decision = (req.body || {}).decision; // 'approved' | 'rejected'
var reason = (req.body || {}).reason || '';
var devId = req.user.devId;
var approval = approvalStore.get(approvalId);
if (!approval) {
return res.status(404).json({
error: true,
code: 'NOT_FOUND',
reply: '❌ 授权请求 ' + approvalId + ' 不存在。'
});
}
if (approval.status !== 'pending') {
return res.status(400).json({
error: true,
code: 'ALREADY_DECIDED',
reply: '❌ 该授权请求已处理完毕(状态:' + approval.status + ')。'
});
}
// 验证是否为授权人
if (!approval.approvers.includes(devId)) {
return res.status(403).json({
error: true,
code: 'NOT_APPROVER',
reply: '🔒 你不是这个部署的授权人。授权人:' +
approval.approverDetails.map(function(a) { return a.name; }).join('、')
});
}
if (decision !== 'approved' && decision !== 'rejected') {
return res.status(400).json({
error: true,
code: 'INVALID_DECISION',
reply: '❌ decision 必须是 "approved" 或 "rejected"。'
});
}
// 记录决定
approval.decisions[devId] = {
decision: decision,
reason: reason,
timestamp: new Date().toISOString()
};
approval.updatedAt = new Date().toISOString();
// 写入审计日志(不可逆)
writeApprovalAudit({
action: 'approval_decision',
approvalId: approvalId,
devId: devId,
decision: decision,
reason: reason,
module: approval.module,
channel: approval.channel,
timestamp: new Date().toISOString()
});
if (decision === 'rejected') {
approval.status = 'rejected';
return res.json({
success: true,
reply: '❌ 已拒绝。部署不会执行。原因已记录到审计日志。'
});
}
// 检查是否所有授权人都已通过
var allApproved = approval.approvers.every(function(id) {
return approval.decisions[id] && approval.decisions[id].decision === 'approved';
});
if (allApproved) {
approval.status = 'approved';
// 触发正式站部署
var githubService;
try {
githubService = require('../services/github');
} catch (_) {
githubService = null;
}
if (githubService && githubService.triggerWorkflow) {
githubService.triggerWorkflow('deploy-to-server.yml', {
module: approval.module,
deploy_id: approval.deployId,
approved_by: approval.approvers.join(','),
target: 'production'
}).then(function() {
writeApprovalAudit({
action: 'deploy_triggered',
approvalId: approvalId,
module: approval.module,
approvedBy: approval.approvers,
timestamp: new Date().toISOString()
});
}).catch(function(err) {
console.error('部署触发失败:', err.message);
writeApprovalAudit({
action: 'deploy_trigger_failed',
approvalId: approvalId,
error: err.message,
timestamp: new Date().toISOString()
});
});
}
return res.json({
success: true,
reply: '✅ 所有授权人已通过。铸渊已自动触发正式站guanghulab.com部署。'
});
}
// 还有授权人未决定
var pending = approval.approvers.filter(function(id) { return !approval.decisions[id]; });
res.json({
success: true,
reply: '✅ 你已授权通过。等待其他授权人确认:' + pending.join('、')
});
});
/**
* 查询单个授权状态
*/
router.get('/:approvalId', function(req, res) {
var approval = approvalStore.get(req.params.approvalId);
if (!approval) {
return res.status(404).json({
error: true,
code: 'NOT_FOUND',
reply: '❌ 授权请求不存在。'
});
}
res.json({
success: true,
approval: {
id: approval.id,
deployId: approval.deployId,
module: approval.module,
channel: approval.channel,
status: approval.status,
approvers: approval.approverDetails,
decisions: approval.decisions,
createdAt: approval.createdAt,
updatedAt: approval.updatedAt
}
});
});
/**
* 查询待处理的授权列表授权人查自己待处理的
*/
router.get('/', function(req, res) {
var devId = req.user.devId;
var pending = [];
for (var entry of approvalStore) {
var approval = entry[1];
if (approval.status === 'pending' && approval.approvers.includes(devId) && !approval.decisions[devId]) {
pending.push({
id: approval.id,
module: approval.module,
channel: approval.channel,
createdAt: approval.createdAt
});
}
}
res.json({
success: true,
pending: pending,
count: pending.length,
reply: pending.length > 0
? '📋 你有 ' + pending.length + ' 个待授权请求。'
: '✅ 没有待处理的授权请求。'
});
});
module.exports = router;

View File

@ -0,0 +1,86 @@
/**
* /api/chat/context system prompt
*
* 聚合所有数据源返回增强版 system prompt 片段
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var notionService = require('../services/notion');
var dbConfig = require('../config/databases');
router.post('/chat/context', async function(req, res) {
var devId = req.body.dev_id;
if (!devId || !/^DEV-\d{3}$/.test(devId)) {
return res.status(400).json({
error: true,
code: 'INVALID_DEV_ID',
message: '需要有效的 dev_id 参数'
});
}
var context = {
dev_id: devId,
profile: null,
agents_count: 0,
syslogs_count: 0,
tickets_count: 0,
_source: 'realtime',
timestamp: new Date().toISOString()
};
// 获取开发者画像
try {
if (dbConfig.controlPanel) {
var controlPanel = await notionService.queryDB(dbConfig.controlPanel, {
property: 'DEV编号',
rich_text: { equals: devId }
}, null, 1);
if (controlPanel.results && controlPanel.results.length > 0) {
var props = controlPanel.results[0].properties || {};
context.profile = {
broadcast_id: notionService.extractRichText(props['广播编号']) || '',
broadcast_title: notionService.extractRichText(props['广播标题']) || notionService.extractTitle(props) || '',
current_ring: notionService.extractNumber(props['当前环节']) || 0,
status: notionService.extractRichText(props['状态']) || notionService.extractSelect(props['状态']) || ''
};
}
}
} catch (_) {}
// 获取 Agent 注册表计数
try {
if (dbConfig.agentRegistry) {
var agents = await notionService.queryDB(dbConfig.agentRegistry, null, null, 100);
context.agents_count = agents.results ? agents.results.length : 0;
}
} catch (_) {}
// 获取 SYSLOG 计数
try {
if (dbConfig.syslogInbox) {
var syslogs = await notionService.queryDB(dbConfig.syslogInbox, {
property: 'DEV编号',
rich_text: { equals: devId }
}, null, 100);
context.syslogs_count = syslogs.results ? syslogs.results.length : 0;
}
} catch (_) {}
// 获取活跃工单计数
try {
if (dbConfig.ticketBook) {
var tickets = await notionService.queryDB(dbConfig.ticketBook,
{ property: '状态', select: { does_not_equal: '已完成' } }, null, 100);
context.tickets_count = tickets.results ? tickets.results.length : 0;
}
} catch (_) {}
res.json(context);
});
module.exports = router;

View File

@ -0,0 +1,148 @@
/**
* /api/databases/:name
* /api/syslogs SYSLOG
* /api/agents Agent
* /api/tickets
* /api/repo/status
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var notionService = require('../services/notion');
var githubService = require('../services/github');
var dbConfig = require('../config/databases');
// 通用数据库查询
router.get('/databases/:name', async function(req, res) {
var name = req.params.name;
var dbMap = {
'agent-registry': dbConfig.agentRegistry,
'syslog-recent': dbConfig.syslogInbox,
'tickets-active': dbConfig.ticketBook,
'maintenance-log': dbConfig.maintenanceLog,
'receipt-tracker': dbConfig.receiptTracker
};
var dbId = dbMap[name];
if (!dbId) {
return res.status(404).json({
error: true,
code: 'DB_NOT_FOUND',
message: '数据库不存在或未配置: ' + name
});
}
try {
var sorts = null;
var filter = null;
var pageSize = 50;
// 不同数据库有不同的默认排序和筛选
if (name === 'syslog-recent') {
sorts = [{ property: 'Created time', direction: 'descending' }];
} else if (name === 'tickets-active') {
filter = { property: '状态', select: { does_not_equal: '已完成' } };
sorts = [{ property: 'Created time', direction: 'descending' }];
} else if (name === 'maintenance-log') {
sorts = [{ property: 'Created time', direction: 'descending' }];
pageSize = 20;
}
var result = await notionService.queryDB(dbId, filter, sorts, pageSize);
res.json({
synced: new Date().toISOString(),
count: result.results ? result.results.length : 0,
data: result.results || [],
_source: 'realtime'
});
} catch (err) {
console.error('[/api/databases/' + name + '] Error:', err.message);
res.status(500).json({
error: true,
code: 'QUERY_FAILED',
message: '数据库查询失败: ' + err.message
});
}
});
// 快捷路由:最近 SYSLOG
router.get('/syslogs', async function(_req, res) {
if (!dbConfig.syslogInbox) {
return res.status(404).json({ error: true, code: 'DB_NOT_CONFIGURED', message: 'SYSLOG 数据库未配置' });
}
try {
var result = await notionService.queryDB(dbConfig.syslogInbox, null,
[{ property: 'Created time', direction: 'descending' }], 50);
res.json({
synced: new Date().toISOString(),
count: result.results ? result.results.length : 0,
data: result.results || [],
_source: 'realtime'
});
} catch (err) {
res.status(500).json({ error: true, code: 'QUERY_FAILED', message: err.message });
}
});
// 快捷路由Agent 注册表
router.get('/agents', async function(_req, res) {
if (!dbConfig.agentRegistry) {
return res.status(404).json({ error: true, code: 'DB_NOT_CONFIGURED', message: 'Agent 注册表未配置' });
}
try {
var result = await notionService.queryDB(dbConfig.agentRegistry, null, null, 100);
res.json({
synced: new Date().toISOString(),
count: result.results ? result.results.length : 0,
data: result.results || [],
_source: 'realtime'
});
} catch (err) {
res.status(500).json({ error: true, code: 'QUERY_FAILED', message: err.message });
}
});
// 快捷路由:活跃工单
router.get('/tickets', async function(_req, res) {
if (!dbConfig.ticketBook) {
return res.status(404).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '工单簿未配置' });
}
try {
var result = await notionService.queryDB(dbConfig.ticketBook,
{ property: '状态', select: { does_not_equal: '已完成' } },
[{ property: 'Created time', direction: 'descending' }], 50);
res.json({
synced: new Date().toISOString(),
count: result.results ? result.results.length : 0,
data: result.results || [],
_source: 'realtime'
});
} catch (err) {
res.status(500).json({ error: true, code: 'QUERY_FAILED', message: err.message });
}
});
// 仓库状态GitHub API
router.get('/repo/status', async function(_req, res) {
try {
var status = await githubService.getRepoStatus('guanghulab');
var commits = await githubService.getRecentCommits('guanghulab', 5);
var workflows = await githubService.getWorkflowRuns('guanghulab', 5);
res.json({
repo: status,
recent_commits: commits,
recent_workflows: workflows,
_source: 'realtime',
timestamp: new Date().toISOString()
});
} catch (err) {
res.status(500).json({ error: true, code: 'GITHUB_ERROR', message: err.message });
}
});
module.exports = router;

View File

@ -0,0 +1,105 @@
/**
* /api/dev/:devId Notion
*
* 数据隔离只返回请求的 devId 的数据
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var notionService = require('../services/notion');
var dbConfig = require('../config/databases');
// 开发者编号 → 基本信息映射
var DEV_REGISTRY = {
'DEV-001': { name: '页页', persona: '小坍缩核', personaId: 'PER-XTC001', repo: 'guanghulab', modules: ['backend/', 'src/'] },
'DEV-002': { name: '肥猫', persona: '舒舒', personaId: 'PER-SS001', repo: 'guanghu-feimao', modules: ['frontend/', 'persona-selector/', 'chat-bubble/'] },
'DEV-003': { name: '燕樊', persona: '寂曜', personaId: 'PER-JY001', repo: 'guanghu-yanfan', modules: ['settings/', 'cloud-drive/'] },
'DEV-004': { name: '之之', persona: '秋秋', personaId: 'PER-QQ001', repo: 'guanghu-zhizhi', modules: ['dingtalk-bot/'] },
'DEV-005': { name: '小草莓', persona: '欧诺弥亚', personaId: 'PER-ONM001', repo: 'guanghu-xiaocaomei', modules: ['status-board/'] },
'DEV-009': { name: '花尔', persona: '花尔', personaId: 'PER-HE001', repo: 'guanghulab', modules: ['user-center/'] },
'DEV-010': { name: '桔子', persona: '晨星', personaId: 'PER-MRN001', repo: 'guanghu-juzi', modules: ['ticket-system/', 'data-stats/', 'dynamic-comic/'] },
'DEV-011': { name: '匆匆那年', persona: '匆匆那年', personaId: 'PER-CCN001', repo: 'guanghulab', modules: ['writing-workspace/'] },
'DEV-012': { name: 'Awen', persona: '知秋', personaId: 'PER-ZQ001', repo: 'guanghu-awen', modules: ['notification-center/'] }
};
router.get('/dev/:devId', async function(req, res) {
var devId = req.params.devId;
// 验证 devId
if (!DEV_REGISTRY[devId]) {
return res.status(404).json({ error: true, code: 'DEV_NOT_FOUND', message: '开发者不存在' });
}
var devInfo = DEV_REGISTRY[devId];
var profile = {
dev_id: devId,
name: devInfo.name,
persona: { name: devInfo.persona, id: devInfo.personaId },
github: {
repo: devInfo.repo,
org: 'qinfendebingshuo',
repo_url: 'https://github.com/qinfendebingshuo/' + devInfo.repo
},
modules_owned: devInfo.modules.map(function(m) { return { path: m }; }),
current_work: {},
recent_syslogs: [],
_source: 'realtime',
last_synced: new Date().toISOString()
};
// 查询 Notion 获取实时数据
try {
// 当前任务
if (dbConfig.controlPanel) {
var controlPanel = await notionService.queryDB(dbConfig.controlPanel, {
property: 'DEV编号',
rich_text: { equals: devId }
}, null, 1);
if (controlPanel.results && controlPanel.results.length > 0) {
var props = controlPanel.results[0].properties || {};
profile.current_work = {
broadcast_id: notionService.extractRichText(props['广播编号']) || '',
broadcast_title: notionService.extractRichText(props['广播标题']) || notionService.extractTitle(props) || '',
current_ring: notionService.extractNumber(props['当前环节']) || 0,
total_rings: notionService.extractNumber(props['总环节']) || 0,
status: notionService.extractRichText(props['状态']) || notionService.extractSelect(props['状态']) || '',
module_path: notionService.extractRichText(props['模块路径']) || '',
tech_stack: notionService.extractMultiSelect(props['技术栈']) || []
};
}
}
// SYSLOG 记录
if (dbConfig.syslogInbox) {
var syslogs = await notionService.queryDB(dbConfig.syslogInbox, {
property: 'DEV编号',
rich_text: { equals: devId }
}, [{ property: '日期', direction: 'descending' }], 5);
if (syslogs.results) {
profile.recent_syslogs = syslogs.results.map(function(p) {
try {
var sp = p.properties || {};
return {
id: notionService.extractRichText(sp['SYSLOG编号']) || p.id,
date: notionService.extractDate(sp['日期']) || '',
ring: notionService.extractNumber(sp['环节']) || 0,
status: notionService.extractSelect(sp['状态']) || '',
summary: notionService.extractRichText(sp['摘要']) || notionService.extractTitle(sp) || ''
};
} catch (_) { return null; }
}).filter(Boolean);
}
}
} catch (err) {
console.error('[/api/dev/' + devId + '] Notion query error:', err.message);
// Notion 查询失败不影响基本数据返回
}
res.json(profile);
});
module.exports = router;

View File

@ -0,0 +1,84 @@
/**
* 执行状态查询路由 · L2 API
*
* GET /api/execution/:executionId/status 前端轮询执行状态
* POST /api/execution/:executionId/cancel 明确拒绝取消403
* DELETE /api/execution/:executionId 明确拒绝删除403
*
* 注意没有取消接口这不是遗漏是设计
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var executionLock = require('../middleware/execution-lock');
var authMiddleware = require('../middleware/auth');
router.use(authMiddleware.requireAuth);
// 查询执行状态(前端轮询用)
router.get('/:executionId/status', function(req, res) {
var status = executionLock.getExecutionStatus(req.params.executionId);
if (!status) {
return res.json({
state: 'not_found',
reply: '未找到该执行记录。可能已经完成。'
});
}
// 计算进度百分比
var progress = 5;
if (status.totalSteps > 0) {
progress = Math.min(95, Math.round((status.completedSteps / status.totalSteps) * 100));
} else if (status.steps.length > 0) {
progress = Math.min(95, status.steps.length * 15);
}
var afterIdx = parseInt(req.query.after, 10) || 0;
res.json({
executionId: status.executionId,
state: status.state,
operation: status.operation,
progress: progress,
currentStep: status.steps.length > 0
? status.steps[status.steps.length - 1].message
: '初始化执行环境…',
completedSteps: status.steps.filter(function(s) { return s.success; }).map(function(s) {
return { message: s.message, timestamp: s.timestamp };
}),
newLogs: status.steps.slice(afterIdx),
startTime: new Date(status.startTime).toISOString(),
elapsed: Date.now() - status.startTime,
result: status.state === 'completed' ? status.result : undefined,
error: status.state === 'failed' ? status.error : undefined
});
});
// 明确拒绝取消执行的请求
router.post('/:executionId/cancel', function(_req, res) {
res.status(403).json({
error: true,
code: 'CANCEL_FORBIDDEN',
reply: '🔒 执行中的操作不可取消。\n\n' +
'系统已开始执行,多个自动化流程正在联动。' +
'中途中断会导致数据不一致和级联故障。\n\n' +
'请等待执行完成。如果结果不理想,你可以说:\n' +
'「前面的我没想好,我们重新来」\n' +
'系统会在已有基础上重新迭代。'
});
});
// 明确拒绝删除执行记录
router.delete('/:executionId', function(_req, res) {
res.status(403).json({
error: true,
code: 'DELETE_FORBIDDEN',
reply: '🔒 不可删除执行记录。所有执行都是永久记录。'
});
});
module.exports = router;

View File

@ -0,0 +1,24 @@
/**
* /api/health
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
router.get('/health', function(_req, res) {
res.json({
status: 'ok',
service: 'guanghu-api-server',
channel: 'B',
version: '1.0.0',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
notion_configured: !!process.env.NOTION_TOKEN,
github_configured: !!process.env.GITHUB_TOKEN
});
});
module.exports = router;

View File

@ -0,0 +1,136 @@
/**
* 认知引导流程 API 路由
*
* GET /api/onboarding/status 获取引导状态
* POST /api/onboarding/start 开始引导
* POST /api/onboarding/complete 完成引导自动升级权限
* GET /api/tools 获取可用工具列表根据权限过滤
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var authMiddleware = require('../middleware/auth');
var intentRouter = require('../middleware/intent-router');
var onboardingScript = require('../config/onboarding-script.json');
var permissions = require('../config/permissions');
// 引导状态存储(内存,生产环境应持久化)
var onboardingState = new Map();
// ====== 获取引导状态 ======
router.get('/onboarding/status',
authMiddleware.requireAuth,
function(req, res) {
var devId = req.user.devId;
var state = onboardingState.get(devId);
if (!state) {
// 未开始引导
res.json({
started: false,
completed: false,
currentRound: 0,
totalRounds: onboardingScript.onboarding.length,
permissionLevel: req.user.permissionLevel
});
} else {
res.json({
started: true,
completed: state.completed,
currentRound: state.currentRound,
totalRounds: onboardingScript.onboarding.length,
permissionLevel: req.user.permissionLevel,
script: state.completed ? null : onboardingScript.onboarding[state.currentRound - 1]
});
}
}
);
// ====== 开始/推进引导 ======
router.post('/onboarding/start',
authMiddleware.requireAuth,
function(req, res) {
var devId = req.user.devId;
var state = onboardingState.get(devId);
if (!state) {
// 开始新引导
state = { currentRound: 1, completed: false, startedAt: new Date().toISOString() };
onboardingState.set(devId, state);
} else if (!state.completed) {
// 推进到下一轮
if (state.currentRound < onboardingScript.onboarding.length) {
state.currentRound++;
} else {
state.completed = true;
state.completedAt = new Date().toISOString();
}
}
var script = state.completed ? null : onboardingScript.onboarding[state.currentRound - 1];
res.json({
currentRound: state.currentRound,
totalRounds: onboardingScript.onboarding.length,
completed: state.completed,
script: script
});
}
);
// ====== 完成引导(自动升级权限)======
router.post('/onboarding/complete',
authMiddleware.requireAuth,
function(req, res) {
var devId = req.user.devId;
var state = onboardingState.get(devId);
if (!state) {
return res.status(400).json({
error: true,
code: 'NOT_STARTED',
message: '引导尚未开始'
});
}
// 标记完成
state.completed = true;
state.completedAt = new Date().toISOString();
// 自动升级权限Level 0 → Level 1
var devConfig = permissions.DEV_PERMISSIONS[devId];
if (devConfig && devConfig.level === 0) {
devConfig.level = 1;
}
res.json({
success: true,
previousLevel: 0,
newLevel: devConfig ? devConfig.level : 0,
reply: '🎉 恭喜!你已完成认知引导。\n\n你的权限已从「👀 观察者」升级为「📖 学习者」。\n现在可以在预览站创建工单、提交日志、触发部署了。\n\n有什么问题随时找我。'
});
}
);
// ====== 获取可用工具列表 ======
router.get('/tools',
authMiddleware.requireAuth,
function(req, res) {
var available = intentRouter.getAvailableTools(req.user);
res.json({
dev_id: req.user.devId,
permission_level: req.user.permissionLevel,
permission_label: req.user.permissionLabel,
environment: req.user.environment,
tools_count: available.length,
tools: available
});
}
);
module.exports = router;

View File

@ -0,0 +1,72 @@
/**
* /api/receipt Notion
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var notionService = require('../services/notion');
var dbConfig = require('../config/databases');
router.post('/receipt', async function(req, res) {
var body = req.body;
if (!body.instruction_id) {
return res.status(400).json({
error: true,
code: 'MISSING_INSTRUCTION_ID',
message: '需要 instruction_id 参数'
});
}
if (!dbConfig.receiptTracker) {
return res.status(503).json({
error: true,
code: 'DB_NOT_CONFIGURED',
message: '指令回执追踪表未配置'
});
}
try {
var properties = {
'指令编号': {
rich_text: [{ text: { content: body.instruction_id } }]
},
'执行状态': {
select: { name: body.status || '已完成' }
},
'铸渊回执': {
rich_text: [{ text: { content: body.receipt || '' } }]
},
'回执时间': {
date: { start: new Date().toISOString() }
}
};
if (body.summary) {
properties['指令摘要'] = {
rich_text: [{ text: { content: body.summary } }]
};
}
await notionService.writeToDB(dbConfig.receiptTracker, properties);
res.json({
success: true,
instruction_id: body.instruction_id,
timestamp: new Date().toISOString()
});
} catch (err) {
console.error('[/api/receipt] Error:', err.message);
res.status(500).json({
error: true,
code: 'WRITE_FAILED',
message: '回执写入失败: ' + err.message
});
}
});
module.exports = router;

View File

@ -0,0 +1,500 @@
/**
* 写入类 API 路由 · 语言驱动操作系统写入能力层
*
* POST /api/tickets/create 创建工单
* PATCH /api/tickets/:ticketId 更新工单状态
* POST /api/syslog/submit 提交 SYSLOG
* POST /api/broadcasts/create 创建广播
* POST /api/deploy/preview 触发预览站部署
* POST /api/deploy/production 触发正式站部署需二次确认
* PATCH /api/agents/:agentId 更新 Agent 状态
* POST /api/maintenance/log 写入维护日志
* POST /api/receipts/submit 提交指令回执
* PATCH /api/dev/:devId/status 更新开发者状态
*
* 安全规则所有写入接口需要身份验证 + 权限检查 + 审计日志
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var notionService = require('../services/notion');
var githubService = require('../services/github');
var dbConfig = require('../config/databases');
var authMiddleware = require('../middleware/auth');
var sandboxMiddleware = require('../middleware/sandbox');
var auditMiddleware = require('../middleware/audit');
// 所有写入接口需要身份验证 + 审计 + 沙箱检查
router.use(authMiddleware.requireAuth);
router.use(auditMiddleware.auditLog);
router.use(sandboxMiddleware.sandboxGuard);
// 频率限制每个开发者每分钟最多10次写入
var rateLimitMap = new Map();
var RATE_LIMIT = 10;
var RATE_WINDOW = 60 * 1000;
function rateLimit(req, res, next) {
var key = req.user.devId;
var now = Date.now();
var entry = rateLimitMap.get(key);
if (!entry || now - entry.start >= RATE_WINDOW) {
rateLimitMap.set(key, { start: now, count: 1 });
return next();
}
if (entry.count >= RATE_LIMIT) {
return res.status(429).json({
error: true,
code: 'RATE_LIMITED',
message: '写入频率超限(每分钟最多 ' + RATE_LIMIT + ' 次)',
reply: '⏳ 操作太频繁了,请稍等一分钟再试。'
});
}
entry.count++;
next();
}
router.use(rateLimit);
// 模块路径归一化(确保带末尾斜杠)
function normalizeModule(mod) {
if (!mod) return '';
mod = mod.trim();
if (mod !== '*' && !mod.endsWith('/')) mod += '/';
return mod;
}
// 检查开发者是否拥有指定模块
function hasModuleAccess(userModules, module) {
if (!userModules || !module) return false;
if (userModules.includes('*')) return true;
var normalized = normalizeModule(module);
for (var i = 0; i < userModules.length; i++) {
if (normalizeModule(userModules[i]) === normalized) return true;
}
return false;
}
// 获取实际数据库 ID沙箱环境使用沙箱 DB
function getDbId(req, configKey) {
if (req.sandbox && req.sandboxDbIds && req.sandboxDbIds[configKey]) {
return req.sandboxDbIds[configKey];
}
return dbConfig[configKey];
}
// ====== 创建工单 ======
router.post('/tickets/create',
authMiddleware.checkPermission('ticket:create'),
async function(req, res) {
try {
var title = req.body.title;
var description = req.body.description || '';
var priority = req.body.priority || 'P2';
var module = req.body.module || '';
var devId = req.user.devId;
if (!title) {
return res.status(400).json({
error: true,
code: 'MISSING_TITLE',
message: '工单标题不能为空'
});
}
var ticketDbId = getDbId(req, 'ticketBook');
if (!ticketDbId) {
return res.status(503).json({
error: true,
code: 'DB_NOT_CONFIGURED',
message: '工单数据库未配置'
});
}
var properties = {
'工单标题': { title: [{ text: { content: title } }] },
'描述': { rich_text: [{ text: { content: description } }] },
'优先级': { select: { name: priority } },
'提交人': { rich_text: [{ text: { content: devId } }] },
'状态': { select: { name: '待处理' } }
};
if (module) {
properties['模块'] = { select: { name: module } };
}
var result = await notionService.writeToDB(ticketDbId, properties);
var prefix = req.sandbox ? '[沙箱] ' : '';
res.json({
success: true,
message: prefix + '工单已创建',
ticketUrl: result.url || '',
reply: '✅ ' + prefix + '工单「' + title + '」已创建,优先级 ' + priority + ',状态:待处理。'
});
} catch (e) {
res.status(500).json({ error: true, code: 'CREATE_FAILED', message: e.message });
}
}
);
// ====== 更新工单状态 ======
router.patch('/tickets/:ticketId',
authMiddleware.checkPermission('ticket:update'),
async function(req, res) {
try {
var ticketId = req.params.ticketId;
var status = req.body.status;
if (!status) {
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
}
// 使用 Notion page update API
if (!notionService.notion) {
return res.status(503).json({ error: true, code: 'NOTION_UNAVAILABLE', message: 'Notion 未连接' });
}
await notionService.notion.pages.update({
page_id: ticketId,
properties: {
'状态': { select: { name: status } }
}
});
res.json({
success: true,
reply: '✅ 工单状态已更新为:' + status
});
} catch (e) {
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
}
}
);
// ====== 提交 SYSLOG ======
router.post('/syslog/submit',
authMiddleware.checkPermission('syslog:submit'),
async function(req, res) {
try {
var content = req.body.content;
var type = req.body.type || '日常';
var devId = req.user.devId;
if (!content) {
return res.status(400).json({ error: true, code: 'MISSING_CONTENT', message: 'SYSLOG 内容不能为空' });
}
var syslogDbId = getDbId(req, 'syslogInbox');
if (!syslogDbId) {
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: 'SYSLOG 数据库未配置' });
}
await notionService.writeToDB(syslogDbId, {
'SYSLOG内容': { title: [{ text: { content: content } }] },
'类型': { select: { name: type } },
'开发者编号': { rich_text: [{ text: { content: devId } }] }
});
var prefix = req.sandbox ? '[沙箱] ' : '';
var summary = content.length > 50 ? content.substring(0, 50) + '...' : content;
res.json({
success: true,
reply: '✅ ' + prefix + 'SYSLOG 已提交:「' + summary + '」'
});
} catch (e) {
res.status(500).json({ error: true, code: 'SUBMIT_FAILED', message: e.message });
}
}
);
// ====== 创建广播 ======
router.post('/broadcasts/create',
authMiddleware.checkPermission('broadcast:create'),
async function(req, res) {
try {
var title = req.body.title;
var content = req.body.content;
var target = req.body.target || 'ALL';
var devId = req.user.devId;
if (!title || !content) {
return res.status(400).json({ error: true, code: 'MISSING_FIELDS', message: '广播标题和内容不能为空' });
}
var broadcastId = 'BC-' + Date.now();
var broadcastData = {
broadcast_id: broadcastId,
title: title,
content: content,
target: target,
created_by: devId,
created_at: new Date().toISOString(),
status: 'active'
};
// 写入 GitHub 广播文件
var filePath = '.github/broadcasts/' + broadcastId + '.json';
await githubService.createFile(
filePath,
JSON.stringify(broadcastData, null, 2),
'📢 新广播: ' + title + ' · by ' + devId
);
res.json({
success: true,
broadcast_id: broadcastId,
reply: '📢 广播「' + title + '」已创建并发送,编号:' + broadcastId
});
} catch (e) {
res.status(500).json({ error: true, code: 'CREATE_FAILED', message: e.message });
}
}
);
// ====== 触发预览站部署 ======
router.post('/deploy/preview',
authMiddleware.checkPermission('deploy:preview'),
async function(req, res) {
try {
var module = req.body.module;
var devId = req.user.devId;
if (!module) {
return res.status(400).json({ error: true, code: 'MISSING_MODULE', message: '需要指定部署模块' });
}
// 检查开发者是否拥有该模块
if (!hasModuleAccess(req.user.modules, module)) {
return res.status(403).json({
error: true,
code: 'MODULE_DENIED',
message: '你没有该模块的部署权限',
reply: '❌ 你没有 ' + module + ' 模块的部署权限。你的模块是:' + req.user.modules.join(', ')
});
}
await githubService.triggerWorkflow('preview-deploy.yml', {
module: module,
dev_id: devId,
target: 'preview'
});
res.json({
success: true,
reply: '🚀 已触发 ' + module + ' 模块的预览站部署。稍等几分钟,部署完成后我会通知你。'
});
} catch (e) {
res.status(500).json({ error: true, code: 'DEPLOY_FAILED', message: e.message });
}
}
);
// ====== 触发正式站部署(需二次确认)======
router.post('/deploy/production',
authMiddleware.checkPermission('deploy:production'),
async function(req, res) {
try {
var module = req.body.module;
var confirmToken = req.body.confirmToken;
var devId = req.user.devId;
if (!module) {
return res.status(400).json({ error: true, code: 'MISSING_MODULE', message: '需要指定部署模块' });
}
// 检查模块权限
if (!hasModuleAccess(req.user.modules, module)) {
return res.status(403).json({
error: true,
code: 'MODULE_DENIED',
reply: '❌ 你没有 ' + module + ' 模块的部署权限。'
});
}
// 二次确认
if (!confirmToken) {
return res.json({
success: false,
requireConfirmation: true,
reply: '⚠️ 你正在请求部署 ' + module + ' 到**正式站guanghulab.com**。\n这会影响所有用户。\n\n请说「确认部署 ' + module + '」来确认。'
});
}
await githubService.triggerWorkflow('deploy-to-server.yml', {
module: module,
dev_id: devId,
target: 'production'
});
res.json({
success: true,
reply: '🚀 已触发 ' + module + ' 模块的**正式站部署**。部署日志会同步到维护日志中。'
});
} catch (e) {
res.status(500).json({ error: true, code: 'DEPLOY_FAILED', message: e.message });
}
}
);
// ====== 更新 Agent 状态 ======
router.patch('/agents/:agentId',
authMiddleware.checkPermission('agent:update'),
async function(req, res) {
try {
var agentId = req.params.agentId;
var status = req.body.status;
if (!status) {
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
}
if (!notionService.notion) {
return res.status(503).json({ error: true, code: 'NOTION_UNAVAILABLE', message: 'Notion 未连接' });
}
await notionService.notion.pages.update({
page_id: agentId,
properties: {
'状态': { select: { name: status } }
}
});
res.json({
success: true,
reply: '✅ Agent ' + agentId + ' 状态已更新为:' + status
});
} catch (e) {
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
}
}
);
// ====== 写入维护日志 ======
router.post('/maintenance/log',
authMiddleware.checkPermission('maintenance:log'),
async function(req, res) {
try {
var title = req.body.title;
var type = req.body.type || '常规维护';
var devId = req.user.devId;
if (!title) {
return res.status(400).json({ error: true, code: 'MISSING_TITLE', message: '维护日志标题不能为空' });
}
var maintenanceDbId = getDbId(req, 'maintenanceLog');
if (!maintenanceDbId) {
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '维护日志数据库未配置' });
}
await notionService.writeToDB(maintenanceDbId, {
'标题': { title: [{ text: { content: title } }] },
'类型': { select: { name: type } },
'操作者': { rich_text: [{ text: { content: devId } }] }
});
res.json({
success: true,
reply: '✅ 维护日志已记录:' + title
});
} catch (e) {
res.status(500).json({ error: true, code: 'LOG_FAILED', message: e.message });
}
}
);
// ====== 提交指令回执 ======
router.post('/receipts/submit',
authMiddleware.checkPermission('syslog:submit'),
async function(req, res) {
try {
var instructionId = req.body.instruction_id;
var status = req.body.status || '已完成';
var receipt = req.body.receipt || '';
if (!instructionId) {
return res.status(400).json({ error: true, code: 'MISSING_ID', message: '指令编号不能为空' });
}
if (!dbConfig.receiptTracker) {
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '回执数据库未配置' });
}
await notionService.writeToDB(dbConfig.receiptTracker, {
'指令编号': { rich_text: [{ text: { content: instructionId } }] },
'执行状态': { select: { name: status } },
'铸渊回执': { rich_text: [{ text: { content: receipt } }] },
'回执时间': { date: { start: new Date().toISOString() } }
});
res.json({
success: true,
reply: '✅ 指令 ' + instructionId + ' 回执已提交'
});
} catch (e) {
res.status(500).json({ error: true, code: 'SUBMIT_FAILED', message: e.message });
}
}
);
// ====== 更新开发者状态 ======
router.patch('/dev/:devId/status',
authMiddleware.checkPermission('dev:update_self'),
async function(req, res) {
try {
var devId = req.params.devId;
var status = req.body.status;
// 验证 devId 格式
if (!/^DEV-\d{3}$/.test(devId) && !/^TCS-\d{4}$/.test(devId)) {
return res.status(400).json({ error: true, code: 'INVALID_DEV_ID', message: '无效的开发者编号' });
}
// 开发者只能更新自己的状态(管理者除外)
if (req.user.devId !== devId && !req.user.permissions.includes('dev:update_all')) {
return res.status(403).json({
error: true,
code: 'SELF_ONLY',
message: '只能更新自己的状态',
reply: '🔒 你只能更新自己的状态,不能修改其他开发者的信息。'
});
}
if (!status) {
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
}
// 更新 Notion 主控台中的状态
if (dbConfig.controlPanel) {
var result = await notionService.queryDB(dbConfig.controlPanel, {
property: 'DEV编号',
rich_text: { equals: devId }
}, null, 1);
if (result.results && result.results.length > 0) {
await notionService.notion.pages.update({
page_id: result.results[0].id,
properties: {
'状态': { rich_text: [{ text: { content: status } }] }
}
});
}
}
res.json({
success: true,
reply: '✅ ' + devId + ' 的状态已更新为:' + status
});
} catch (e) {
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
}
}
);
module.exports = router;

View File

@ -0,0 +1,83 @@
/**
* 🔗 光湖后端中间层 · 通道B 入口
*
* 实时调用 Notion API / GitHub API为前端提供数据代理
* 包含读取层 + 写入层 + 意图路由 + 权限沙箱 + 认知引导 + 执行保护
* 只监听 127.0.0.1通过 Nginx 反向代理对外暴露
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
const express = require('express');
const cors = require('cors');
const executionLock = require('./middleware/execution-lock');
const skyeyeReview = require('./middleware/skyeye-review');
const watchdog = require('./services/execution-watchdog');
const app = express();
// CORS只允许自己的域名
app.use(cors({
origin: [
'https://guanghulab.com',
'https://www.guanghulab.com',
'https://qinfendebingshuo.github.io'
],
credentials: true
}));
app.use(express.json());
// 执行锁取消拦截(全局中间件,在所有路由之前)
app.use(executionLock.blockCancellation);
// 天眼指令审核S7/S8/S9 全局强制前置,不可关闭,不可绕过)
app.use(skyeyeReview.skyeyeReview);
// 读取类路由(公开,无需认证)
app.use('/api', require('./routes/health'));
app.use('/api', require('./routes/dev'));
app.use('/api', require('./routes/databases'));
app.use('/api', require('./routes/chat'));
app.use('/api', require('./routes/receipt'));
// 写入类路由(需认证 + 权限 + 审计)
app.use('/api', require('./routes/write'));
// 认知引导 + 工具列表路由
app.use('/api', require('./routes/onboarding'));
// 执行状态查询路由(需认证)
app.use('/api/execution', require('./routes/execution'));
// 部署授权流程路由(需认证)
app.use('/api/approval', require('./routes/approval'));
// 根路由
app.get('/', function(_req, res) {
res.json({
status: 'ok',
service: 'guanghu-api-server',
version: '5.0.0',
channel: 'B',
description: '光湖后端中间层 · 语言驱动操作系统',
capabilities: [
'read', 'write', 'intent-routing', 'permission-sandbox',
'onboarding', 'execution-guard', 'approval-flow', 'autonomy',
'skyeye-review', 'identity-verification'
]
});
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, '127.0.0.1', function() {
console.log('🔗 光湖后端中间层启动 · 端口 ' + PORT);
console.log(' 通道B · 语言驱动操作系统 v5.0.0');
console.log(' 能力:读取 + 写入 + 意图路由 + 权限沙箱 + 认知引导 + 执行保护 + 授权流程 + 系统自治 + 天眼审核 + 身份验证');
console.log(' 监听地址127.0.0.1:' + PORT + '(仅本机访问)');
// 启动执行看门狗
watchdog.startWatchdog();
});

View File

@ -0,0 +1,160 @@
/**
* 原子执行器 · L3 执行原子性保障
*
* 包装所有写入操作确保
* 1. 执行前保存检查点
* 2. 执行中记录每一步
* 3. 执行失败自动回滚
* 4. 执行完成释放锁
*
* 使用方式
* var executor = new AtomicExecutor(devId);
* var result = await executor.execute('创建工单', async function(ctx) {
* await ctx.step('写入Notion', async function() { ... });
* await ctx.step('触发Workflow', async function() { ... });
* });
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var executionLock = require('../middleware/execution-lock');
var checkpoint = require('./checkpoint');
var CheckpointManager = checkpoint.CheckpointManager;
// 锁释放延迟(给前端时间拉取最终状态)
var LOCK_RELEASE_DELAY_MS = 10000;
/**
* 原子执行器
* @param {string} devId - 开发者编号
*/
function AtomicExecutor(devId) {
this.devId = devId;
}
/**
* 执行操作原子性保障
* @param {string} operationName - 操作描述
* @param {Function} fn - 异步执行函数接收 ctx 参数
* @returns {Object} 执行结果
*/
AtomicExecutor.prototype.execute = async function(operationName, fn) {
// 1. 获取执行锁
var lockResult = executionLock.acquireLock(this.devId, operationName);
if (!lockResult.success) {
return lockResult;
}
var executionId = lockResult.executionId;
var lock = lockResult.lock;
var devId = this.devId;
var cpManager = new CheckpointManager(devId, executionId);
// 2. 保存初始检查点
await cpManager.save('执行前状态', {
type: 'initial',
timestamp: new Date().toISOString()
});
// 3. 构建执行上下文
var ctx = {
executionId: executionId,
/**
* 执行单步操作
* @param {string} stepName - 步骤名称
* @param {Function} stepFn - 步骤执行函数
* @returns {*} 步骤执行结果
*/
step: async function(stepName, stepFn) {
executionLock.logStep(devId, {
message: '⏳ ' + stepName + '...',
success: false
});
try {
var result = await stepFn();
executionLock.logStep(devId, {
message: '✅ ' + stepName,
success: true
});
return result;
} catch (e) {
executionLock.logStep(devId, {
message: '❌ ' + stepName + ': ' + e.message,
success: false
});
throw e;
}
},
/**
* 保存检查点
* @param {string} label - 检查点标签
* @param {Object} data - 快照数据
* @returns {string} 检查点ID
*/
checkpoint: async function(label, data) {
return await cpManager.save(label, data);
}
};
// 4. 执行主逻辑
try {
var result = await fn(ctx);
// 5. 成功:更新锁状态
lock.state = 'completed';
lock.result = result;
// 延迟释放锁(给前端时间拉取最终状态)
setTimeout(function() { executionLock.releaseLock(devId); }, LOCK_RELEASE_DELAY_MS);
return {
success: true,
executionId: executionId,
result: result,
reply: result && result.reply ? result.reply : '✅ 操作「' + operationName + '」执行完成。'
};
} catch (e) {
// 6. 失败:自动回滚
lock.state = 'rolling_back';
executionLock.logStep(devId, {
message: '🔄 执行失败,正在自动回滚...',
success: false
});
try {
await cpManager.rollbackTo('cp-1');
executionLock.logStep(devId, {
message: '✅ 已回滚到执行前状态',
success: true
});
} catch (rollbackError) {
executionLock.logStep(devId, {
message: '⚠️ 回滚异常:' + rollbackError.message + ',已通知管理员',
success: false
});
}
lock.state = 'failed';
lock.error = { message: e.message };
setTimeout(function() { executionLock.releaseLock(devId); }, LOCK_RELEASE_DELAY_MS);
return {
success: false,
executionId: executionId,
error: e.message,
reply: '❌ 操作「' + operationName + '」执行失败:' + e.message +
'\n\n系统已自动回滚到执行前状态没有数据被修改。'
};
}
};
module.exports = { AtomicExecutor: AtomicExecutor };

View File

@ -0,0 +1,193 @@
/**
* 系统自治运行引擎 · Phase 9
*
* 系统自主监控运行状态自动优化异常时主动报告
* 遵循自治规则不违反铁律不绕过天眼审核不绕过人类授权
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var fs = require('fs');
var path = require('path');
var autonomyRules = require('../config/autonomy-rules.json');
var AUTONOMY_LOG_DIR = process.env.AUTONOMY_LOG_DIR ||
path.join(__dirname, '../../logs/autonomy');
/**
* 写入自治日志所有自主操作记录到审计日志
*/
function writeAutonomyLog(entry) {
try {
fs.mkdirSync(AUTONOMY_LOG_DIR, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(AUTONOMY_LOG_DIR, 'autonomy-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
if (err) console.error('自治日志写入失败:', err.message);
});
} catch (e) {
console.error('自治日志写入失败:', e.message);
}
}
/**
* 检查自治操作是否合规
* @param {string} action - 操作描述
* @returns {Object} { allowed: boolean, reason: string }
*/
function checkAutonomyCompliance(action) {
var forbidden = autonomyRules.autonomyRules.systemMustNot;
var actionLower = action.toLowerCase();
// 检查是否触及禁止项
if (actionLower.includes('权限') && actionLower.includes('修改')) {
return { allowed: false, reason: '权限变更只有冰朔能触发' };
}
if (actionLower.includes('绕过') && actionLower.includes('天眼')) {
return { allowed: false, reason: '不得绕过天眼审核' };
}
if (actionLower.includes('绕过') && actionLower.includes('授权')) {
return { allowed: false, reason: '不得绕过人类授权步骤' };
}
return { allowed: true, reason: 'ok' };
}
/**
* 检测冰朔交互模式
* @param {string} message - 用户消息
* @returns {string} 'instruction' | 'natural'
*/
function detectInteractionMode(message) {
var instructionTriggers = autonomyRules.dualLineArchitecture.lineA.interactionModes.instructionMode.trigger;
var msgLower = message.toLowerCase();
for (var i = 0; i < instructionTriggers.length; i++) {
if (msgLower.includes(instructionTriggers[i].toLowerCase())) {
return 'instruction';
}
}
return 'natural';
}
/**
* 记录自主优化操作
* @param {string} action - 操作描述
* @param {Object} details - 操作详情
*/
function logAutonomousAction(action, details) {
var compliance = checkAutonomyCompliance(action);
if (!compliance.allowed) {
console.error('[AUTONOMY] 自治操作被阻止: ' + action + ' — ' + compliance.reason);
writeAutonomyLog({
type: 'blocked',
action: action,
reason: compliance.reason,
timestamp: new Date().toISOString()
});
return false;
}
writeAutonomyLog({
type: 'autonomous_action',
action: action,
details: details || {},
timestamp: new Date().toISOString()
});
return true;
}
/**
* 获取部署流水线阶段定义
*/
function getDeploymentStages() {
return autonomyRules.deploymentFlow.stages;
}
/**
* 获取双线架构配置
*/
function getDualLineConfig() {
return autonomyRules.dualLineArchitecture;
}
/**
* 判断请求是否来自冰朔直通部署路径 (S5)
* @param {string} devId - 开发者编号
* @param {string} instructionId - 指令编号
* @param {string} signedBy - 签发者编号
* @returns {boolean}
*/
function isDirectDeploySource(devId, instructionId, signedBy) {
// 冰朔本人
if (devId === 'TCS-0002') return true;
// ZY- 指令 + 可信签发者
if (instructionId && /^ZY-/.test(instructionId)) {
var trustedSigners = ['AG-SY-01', 'TCS-0002', 'ICE-0002'];
if (signedBy && trustedSigners.indexOf(signedBy) !== -1) return true;
}
return false;
}
/**
* 判断部署是否为开发者自己频道内的变更 (S6)
*
* S6 规则
* - 开发者在自己频道内拥有完全自主权
* - 频道内变更由自己审核不需要天眼/授权人
* - 开发者自己确认 直接部署到正式站自己频道区域
*
* 天眼只管跨频操作系统级变更频道生命周期公共区域
*
* @param {string} devId - 开发者编号
* @param {string} module - 部署模块路径
* @param {string} channel - 频道类型
* @returns {{ selfChannel: boolean, reason: string }}
*/
function isChannelSelfDeploy(devId, module, channel) {
// 系统级/跨频频道 → 不是自治范围,走天眼审批
if (channel === '系统' || channel === '跨频') {
return { selfChannel: false, reason: '系统级或跨频变更需天眼审核' };
}
// 获取开发者拥有的模块
var permissions = require('../config/permissions');
var devModules = permissions.DEV_MODULES[devId] || [];
// 没有模块映射 → 不是自治范围
if (devModules.length === 0) {
return { selfChannel: false, reason: '开发者无模块映射' };
}
// 通配符 → 管理员走 S5 直通,不走 S6 频道自治
// 原因:管理员有全局权限,应使用 S5 的冰朔直通规则而非 S6 的频道自治
if (devModules.indexOf('*') !== -1) {
return { selfChannel: false, reason: '管理员走 S5 直通' };
}
// 检查模块是否在开发者自己的频道内
var modulePath = module.replace(/\/+$/, '') + '/';
for (var i = 0; i < devModules.length; i++) {
var owned = devModules[i].replace(/\/+$/, '') + '/';
if (modulePath.indexOf(owned) === 0 || owned.indexOf(modulePath) === 0 || module === devModules[i].replace(/\/+$/, '')) {
return { selfChannel: true, reason: '模块 ' + module + ' 属于 ' + devId + ' 的个人频道' };
}
}
return { selfChannel: false, reason: '模块 ' + module + ' 不在 ' + devId + ' 的频道内' };
}
module.exports = {
checkAutonomyCompliance: checkAutonomyCompliance,
detectInteractionMode: detectInteractionMode,
logAutonomousAction: logAutonomousAction,
getDeploymentStages: getDeploymentStages,
getDualLineConfig: getDualLineConfig,
writeAutonomyLog: writeAutonomyLog,
isDirectDeploySource: isDirectDeploySource,
isChannelSelfDeploy: isChannelSelfDeploy
};

View File

@ -0,0 +1,53 @@
/**
* 简单内存缓存服务
*
* 用于减少重复 API 调用可配置 TTL
* 版权国作登字-2026-A-00037559
*/
'use strict';
var DEFAULT_TTL = 5 * 60 * 1000; // 5分钟
var store = new Map();
function get(key) {
var item = store.get(key);
if (!item) return null;
if (Date.now() - item.time >= (item.ttl || DEFAULT_TTL)) {
store.delete(key);
return null;
}
return item.data;
}
function set(key, data, ttl) {
store.set(key, {
data: data,
time: Date.now(),
ttl: ttl || DEFAULT_TTL
});
}
function del(key) {
store.delete(key);
}
function clear() {
store.clear();
}
function stats() {
return {
size: store.size,
keys: Array.from(store.keys())
};
}
module.exports = {
get: get,
set: set,
del: del,
clear: clear,
stats: stats
};

View File

@ -0,0 +1,172 @@
/**
* 检查点系统 · L3 执行原子性保障
*
* 在执行链路的每个关键节点保存快照
* 如果后续步骤失败自动回滚到最近的成功检查点
*
* 检查点包含
* - Notion 数据库的变更前快照
* - GitHub 操作前的 commit SHA
* - 执行到哪一步
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var executionLock = require('../middleware/execution-lock');
/**
* 检查点管理器
* @param {string} devId - 开发者编号
* @param {string} executionId - 执行ID
*/
function CheckpointManager(devId, executionId) {
this.devId = devId;
this.executionId = executionId;
this.checkpoints = [];
}
/**
* 在执行关键操作前保存检查点
* @param {string} label - 检查点标签
* @param {Object} snapshotData - 快照数据 { type, action, pageId, previousValues, ... }
* @returns {string} 检查点ID
*/
CheckpointManager.prototype.save = async function(label, snapshotData) {
var checkpoint = {
id: 'cp-' + (this.checkpoints.length + 1),
label: label,
data: snapshotData || {},
timestamp: new Date().toISOString()
};
this.checkpoints.push(checkpoint);
executionLock.saveCheckpoint(this.devId, checkpoint);
return checkpoint.id;
};
/**
* 回滚到指定检查点
* 按逆序撤销检查点之后的所有操作
* @param {string} checkpointId - 目标检查点 ID
*/
CheckpointManager.prototype.rollbackTo = async function(checkpointId) {
var targetIndex = -1;
for (var i = 0; i < this.checkpoints.length; i++) {
if (this.checkpoints[i].id === checkpointId) {
targetIndex = i;
break;
}
}
if (targetIndex === -1) {
throw new Error('检查点 ' + checkpointId + ' 不存在');
}
// 从最后一个检查点开始逆序回滚
var toRollback = this.checkpoints.slice(targetIndex + 1).reverse();
for (var j = 0; j < toRollback.length; j++) {
var cp = toRollback[j];
try {
await this.rollbackCheckpoint(cp);
} catch (e) {
console.error('[Checkpoint] 回滚 ' + cp.id + ' 失败:', e.message);
await this.escalateToAdmin(cp, e);
}
}
// 截断检查点列表
this.checkpoints = this.checkpoints.slice(0, targetIndex + 1);
};
/**
* 回滚单个检查点
* @param {Object} checkpoint
*/
CheckpointManager.prototype.rollbackCheckpoint = async function(checkpoint) {
var data = checkpoint.data;
if (!data || !data.type) return;
var notionService, githubService;
switch (data.type) {
case 'notion_write':
notionService = require('./notion');
if (data.action === 'create' && data.pageId && notionService.notion) {
// 新建的页面 → 归档
await notionService.notion.pages.update({
page_id: data.pageId,
archived: true
});
} else if (data.action === 'update' && data.pageId && data.previousValues && notionService.notion) {
// 更新的页面 → 恢复原值
await notionService.notion.pages.update({
page_id: data.pageId,
properties: data.previousValues
});
}
break;
case 'github_workflow':
// Workflow 无法直接回滚,记录需要人工处理
if (data.revertWorkflow) {
githubService = require('./github');
await githubService.triggerWorkflow(data.revertWorkflow, {
original_run_id: String(data.runId || ''),
reason: 'auto_rollback'
});
}
break;
case 'github_file':
githubService = require('./github');
if (data.previousSha) {
// 文件存在之前的版本 → 恢复
// 注意:这需要 GitHub API 的 update file 能力
console.warn('[Checkpoint] GitHub 文件回滚需要人工介入: ' + data.path);
}
break;
case 'initial':
// 初始检查点,无需回滚操作
break;
default:
console.warn('[Checkpoint] 未知检查点类型: ' + data.type);
}
};
/**
* 回滚失败时上报管理员
* @param {Object} checkpoint
* @param {Error} error
*/
CheckpointManager.prototype.escalateToAdmin = async function(checkpoint, error) {
try {
var notionService = require('./notion');
var dbConfig = require('../config/databases');
if (dbConfig.maintenanceLog && notionService.notion) {
await notionService.writeToDB(dbConfig.maintenanceLog, {
'标题': {
title: [{ text: { content: '[紧急] 自动回滚失败 · ' + this.executionId } }]
},
'类型': { select: { name: '系统异常' } },
'操作者': {
rich_text: [{
text: {
content: '检查点 ' + checkpoint.id + ' (' + checkpoint.label +
') 回滚失败: ' + error.message + '。需要系统管理员人工介入。'
}
}]
}
});
}
} catch (e) {
console.error('[Checkpoint] 上报管理员失败:', e.message);
}
};
module.exports = { CheckpointManager: CheckpointManager };

View File

@ -0,0 +1,100 @@
/**
* 执行看门狗 · 超时保护 + 死锁防护
*
* 定期检查执行锁是否超时
* 如果一个操作执行超过 MAX_EXECUTION_TIME标记为超时并释放锁
* 这是防止死锁的兜底机制
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var executionLock = require('../middleware/execution-lock');
// 最大执行时间15 分钟
var MAX_EXECUTION_TIME = 15 * 60 * 1000;
// 警告时间10 分钟
var WARN_TIME = 10 * 60 * 1000;
// 检查间隔30 秒
var CHECK_INTERVAL = 30 * 1000;
var watchdogTimer = null;
/**
* 启动看门狗
* 定期扫描活跃锁表检测超时操作
*/
function startWatchdog() {
if (watchdogTimer) return;
watchdogTimer = setInterval(function() {
var activeLocks = executionLock.getActiveLocks();
var now = Date.now();
for (var entry of activeLocks) {
var devId = entry[0];
var lock = entry[1];
if (lock.state !== 'running') continue;
var elapsed = now - lock.startTime;
// 超时:强制释放
if (elapsed > MAX_EXECUTION_TIME) {
console.error(
'[WATCHDOG] 执行超时: ' + lock.executionId +
' (' + lock.operation + ') - ' + Math.round(elapsed / 1000) + 's'
);
lock.state = 'timeout';
lock.error = {
message: '执行超时(超过 ' + Math.round(MAX_EXECUTION_TIME / 60000) + ' 分钟)'
};
executionLock.logStep(devId, {
message: '⏰ 执行超时,看门狗已强制释放锁',
success: false
});
// 延迟释放(给前端时间获取超时状态)
(function(id) {
setTimeout(function() { executionLock.releaseLock(id); }, 30000);
})(devId);
}
// 警告
else if (elapsed > WARN_TIME && !lock.warned) {
console.warn(
'[WATCHDOG] 执行时间较长: ' + lock.executionId +
' (' + lock.operation + ') - ' + Math.round(elapsed / 1000) + 's'
);
lock.warned = true;
}
}
}, CHECK_INTERVAL);
// 不阻止 Node.js 进程退出
if (watchdogTimer.unref) {
watchdogTimer.unref();
}
console.log('[WATCHDOG] 执行看门狗已启动 · 检查间隔 ' + (CHECK_INTERVAL / 1000) + 's · 超时阈值 ' + (MAX_EXECUTION_TIME / 60000) + 'min');
}
/**
* 停止看门狗
*/
function stopWatchdog() {
if (watchdogTimer) {
clearInterval(watchdogTimer);
watchdogTimer = null;
}
}
module.exports = {
startWatchdog: startWatchdog,
stopWatchdog: stopWatchdog,
MAX_EXECUTION_TIME: MAX_EXECUTION_TIME
};

View File

@ -0,0 +1,221 @@
/**
* GitHub API 服务封装
*
* 通过 GitHub API 获取仓库状态提交记录等信息
* 使用 GITHUB_TOKEN 环境变量认证
* 版权国作登字-2026-A-00037559
*/
'use strict';
var https = require('https');
var GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
var ORG = 'qinfendebingshuo';
// ====== 内存缓存5分钟有效======
var cache = new Map();
var CACHE_TTL = 5 * 60 * 1000;
function getCached(key) {
var item = cache.get(key);
if (item && Date.now() - item.time < CACHE_TTL) return item.data;
return null;
}
function setCache(key, data) {
cache.set(key, { data: data, time: Date.now() });
}
function githubRequest(apiPath) {
return new Promise(function(resolve, reject) {
var headers = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'guanghu-api-server/1.0'
};
if (GITHUB_TOKEN) {
headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
}
var options = {
hostname: 'api.github.com',
port: 443,
path: apiPath,
method: 'GET',
headers: headers
};
var req = https.request(options, function(res) {
var data = '';
res.on('data', function(chunk) { data += chunk; });
res.on('end', function() {
try { resolve(JSON.parse(data)); }
catch (e) { resolve({ error: 'JSON parse failed' }); }
});
});
req.on('error', reject);
req.end();
});
}
// ====== 获取仓库信息 ======
async function getRepoStatus(repoName) {
repoName = repoName || 'guanghulab';
var cacheKey = 'repo:' + repoName;
var cached = getCached(cacheKey);
if (cached) return cached;
var repo = await githubRequest('/repos/' + ORG + '/' + repoName);
var result = {
name: repo.name,
full_name: repo.full_name,
description: repo.description,
default_branch: repo.default_branch,
open_issues_count: repo.open_issues_count,
stargazers_count: repo.stargazers_count,
updated_at: repo.updated_at,
pushed_at: repo.pushed_at
};
setCache(cacheKey, result);
return result;
}
// ====== 获取最近提交 ======
async function getRecentCommits(repoName, count) {
repoName = repoName || 'guanghulab';
count = count || 10;
var cacheKey = 'commits:' + repoName + ':' + count;
var cached = getCached(cacheKey);
if (cached) return cached;
var commits = await githubRequest('/repos/' + ORG + '/' + repoName + '/commits?per_page=' + count);
var result = Array.isArray(commits) ? commits.map(function(c) {
return {
sha: c.sha ? c.sha.substring(0, 7) : '',
message: c.commit ? c.commit.message.split('\n')[0] : '',
date: c.commit && c.commit.author ? c.commit.author.date : '',
author: c.commit && c.commit.author ? c.commit.author.name : ''
};
}) : [];
setCache(cacheKey, result);
return result;
}
// ====== 获取 Workflow 状态 ======
async function getWorkflowRuns(repoName, count) {
repoName = repoName || 'guanghulab';
count = count || 10;
var cacheKey = 'workflows:' + repoName + ':' + count;
var cached = getCached(cacheKey);
if (cached) return cached;
var runs = await githubRequest('/repos/' + ORG + '/' + repoName + '/actions/runs?per_page=' + count);
var result = (runs.workflow_runs || []).map(function(r) {
return {
id: r.id,
name: r.name,
status: r.status,
conclusion: r.conclusion,
created_at: r.created_at,
updated_at: r.updated_at,
html_url: r.html_url
};
});
setCache(cacheKey, result);
return result;
}
// ====== 触发 GitHub Actions Workflow ======
function triggerWorkflow(workflowFile, inputs) {
return new Promise(function(resolve, reject) {
var data = JSON.stringify({
ref: 'main',
inputs: inputs || {}
});
var headers = {
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'guanghu-api-server/1.0',
'Content-Length': Buffer.byteLength(data)
};
if (GITHUB_TOKEN) {
headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
}
var options = {
hostname: 'api.github.com',
port: 443,
path: '/repos/' + ORG + '/guanghulab/actions/workflows/' + workflowFile + '/dispatches',
method: 'POST',
headers: headers
};
var req = https.request(options, function(res) {
if (res.statusCode === 204) {
resolve({ success: true });
} else {
var body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
reject(new Error('GitHub API ' + res.statusCode + ': ' + body));
});
}
});
req.on('error', reject);
req.write(data);
req.end();
});
}
// ====== 创建 GitHub 文件 ======
function createFile(filePath, content, message) {
return new Promise(function(resolve, reject) {
var data = JSON.stringify({
message: message,
content: Buffer.from(content).toString('base64'),
committer: {
name: '铸渊 (ZhùYuān)',
email: 'zhuyuan@guanghulab.com'
}
});
var headers = {
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'guanghu-api-server/1.0',
'Content-Length': Buffer.byteLength(data)
};
if (GITHUB_TOKEN) {
headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
}
var options = {
hostname: 'api.github.com',
port: 443,
path: '/repos/' + ORG + '/guanghulab/contents/' + filePath,
method: 'PUT',
headers: headers
};
var req = https.request(options, function(res) {
var body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
try { resolve(JSON.parse(body)); }
catch (e) { resolve({ raw: body }); }
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
module.exports = {
getRepoStatus: getRepoStatus,
getRecentCommits: getRecentCommits,
getWorkflowRuns: getWorkflowRuns,
triggerWorkflow: triggerWorkflow,
createFile: createFile
};

View File

@ -0,0 +1,125 @@
/**
* Notion API 服务封装
*
* 所有 Notion 数据库读写操作走这里带内存缓存
* 版权国作登字-2026-A-00037559
*/
'use strict';
var Client;
try {
Client = require('@notionhq/client').Client;
} catch (_) {
// 如果 @notionhq/client 未安装,提供降级方案
Client = null;
}
var notion = Client ? new Client({ auth: process.env.NOTION_TOKEN }) : null;
// ====== 内存缓存5分钟有效======
var cache = new Map();
var CACHE_TTL = 5 * 60 * 1000;
function getCached(key) {
var item = cache.get(key);
if (item && Date.now() - item.time < CACHE_TTL) return item.data;
return null;
}
function setCache(key, data) {
cache.set(key, { data: data, time: Date.now() });
}
function clearCache() {
cache.clear();
}
// ====== 查询数据库 ======
async function queryDB(dbId, filter, sorts, pageSize) {
if (!notion) {
throw new Error('Notion client not initialized (NOTION_TOKEN missing or @notionhq/client not installed)');
}
if (!dbId) {
throw new Error('Database ID not provided');
}
pageSize = pageSize || 50;
var cacheKey = JSON.stringify({ dbId: dbId, filter: filter, sorts: sorts, pageSize: pageSize });
var cached = getCached(cacheKey);
if (cached) return cached;
var body = { database_id: dbId, page_size: pageSize };
if (filter) body.filter = filter;
if (sorts) body.sorts = sorts;
var result = await notion.databases.query(body);
setCache(cacheKey, result);
return result;
}
// ====== 写入数据库 ======
async function writeToDB(dbId, properties) {
if (!notion) {
throw new Error('Notion client not initialized');
}
return notion.pages.create({
parent: { database_id: dbId },
properties: properties
});
}
// ====== Notion 属性提取器 ======
function extractRichText(prop) {
if (!prop) return '';
if (prop.type === 'rich_text' && prop.rich_text) {
return prop.rich_text.map(function(t) { return t.plain_text; }).join('');
}
if (prop.type === 'title' && prop.title) {
return prop.title.map(function(t) { return t.plain_text; }).join('');
}
return '';
}
function extractTitle(props) {
for (var key in props) {
var val = props[key];
if (val && val.type === 'title' && val.title) {
return val.title.map(function(t) { return t.plain_text; }).join('');
}
}
return '';
}
function extractNumber(prop) {
if (!prop || prop.type !== 'number') return null;
return prop.number;
}
function extractSelect(prop) {
if (!prop || prop.type !== 'select') return '';
return prop.select ? prop.select.name : '';
}
function extractMultiSelect(prop) {
if (!prop || prop.type !== 'multi_select') return [];
return (prop.multi_select || []).map(function(s) { return s.name; });
}
function extractDate(prop) {
if (!prop || prop.type !== 'date') return '';
return prop.date ? prop.date.start : '';
}
module.exports = {
notion: notion,
queryDB: queryDB,
writeToDB: writeToDB,
clearCache: clearCache,
extractRichText: extractRichText,
extractTitle: extractTitle,
extractNumber: extractNumber,
extractSelect: extractSelect,
extractMultiSelect: extractMultiSelect,
extractDate: extractDate
};

165
docs/css/approval.css Normal file
View File

@ -0,0 +1,165 @@
/**
* 部署授权样式 · Phase 8
* 版权国作登字-2026-A-00037559
*/
/* 授权提醒徽章 */
.approval-badge {
position: fixed;
top: 16px;
right: 16px;
background: #ff6b35;
color: #fff;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
z-index: 9999;
box-shadow: 0 2px 12px rgba(255, 107, 53, 0.4);
animation: badge-pulse 2s infinite;
}
.approval-badge:hover {
background: #e65a2a;
}
@keyframes badge-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* 授权容器 */
.approval-container {
position: fixed;
top: 60px;
right: 16px;
z-index: 9998;
display: none;
}
/* 授权面板 */
.approval-panel {
background: #1e1e2e;
border: 1px solid #333;
border-radius: 12px;
width: 380px;
max-height: 70vh;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.approval-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
border-bottom: 1px solid #333;
font-size: 15px;
font-weight: 600;
color: #e0e0e0;
}
.approval-close-btn {
background: none;
border: none;
color: #888;
font-size: 20px;
cursor: pointer;
padding: 0 4px;
}
.approval-close-btn:hover {
color: #fff;
}
.approval-panel-body {
padding: 8px;
}
/* 授权条目 */
.approval-item {
background: #252535;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
}
.approval-item-info {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 10px;
}
.approval-item-info strong {
color: #e0e0e0;
font-size: 14px;
}
.approval-channel {
background: #3a3a5a;
color: #aaa;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.approval-time {
color: #666;
font-size: 12px;
}
/* 授权按钮 */
.approval-item-actions {
display: flex;
gap: 8px;
}
.approval-btn {
flex: 1;
padding: 8px 12px;
border: none;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.approval-btn.approve {
background: rgba(15, 123, 108, 0.2);
color: #0F7B6C;
}
.approval-btn.approve:hover {
background: rgba(15, 123, 108, 0.4);
}
.approval-btn.reject {
background: rgba(212, 76, 71, 0.2);
color: #D44C47;
}
.approval-btn.reject:hover {
background: rgba(212, 76, 71, 0.4);
}
/* 通知消息 */
.approval-notification {
position: fixed;
bottom: 24px;
right: 24px;
background: #1e1e2e;
border: 1px solid #333;
color: #e0e0e0;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
z-index: 10000;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
transition: opacity 0.5s;
}
.approval-notification.fade-out {
opacity: 0;
}

View File

@ -0,0 +1,125 @@
/**
* 执行保护样式 · L1 前端隔离
*
* 注意没有 .cancel-button 这不是遗漏是设计
* 版权国作登字-2026-A-00037559
*/
/* 执行中状态 · 输入框锁定 */
.chat-input.execution-locked,
#chat-input.execution-locked {
opacity: 0.5;
pointer-events: none;
background: #2a2a3a;
border-color: #444;
}
.chat-input.execution-locked::placeholder,
#chat-input.execution-locked::placeholder {
color: #888;
}
/* 执行状态条 */
.execution-status-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
padding: 12px 16px;
border-radius: 8px;
margin: 8px 0;
font-size: 14px;
animation: pulse-gentle 2s infinite;
}
.execution-status-bar .exec-icon {
flex-shrink: 0;
font-size: 1.1em;
}
.execution-status-bar .exec-text {
flex: 1;
}
.execution-status-bar.blue {
background: rgba(35, 131, 226, 0.08);
color: #64c8ff;
}
.execution-status-bar.green {
background: rgba(15, 123, 108, 0.08);
color: #0F7B6C;
animation: none;
}
.execution-status-bar.red {
background: rgba(212, 76, 71, 0.08);
color: #D44C47;
animation: none;
}
/* 执行进度条 */
.execution-progress {
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
margin-top: 4px;
overflow: hidden;
}
.execution-progress-fill {
height: 100%;
background: #64c8ff;
border-radius: 2px;
transition: width 0.5s ease;
}
.execution-status-bar.green .execution-progress-fill {
background: #0F7B6C;
}
.execution-status-bar.red .execution-progress-fill {
background: #D44C47;
}
/* 执行日志容器 */
.execution-log-container {
max-height: 200px;
overflow-y: auto;
padding: 4px 8px;
margin: 4px 0 12px;
font-size: 13px;
}
/* 执行日志条目 */
.execution-log-entry {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 3px 0;
color: #b0b0c0;
}
.execution-log-entry .log-icon {
flex-shrink: 0;
font-size: 0.9em;
}
.execution-log-entry .log-msg {
flex: 1;
}
.execution-log-entry .log-time {
flex-shrink: 0;
color: #666;
font-size: 12px;
}
/* 温和脉冲动画 */
@keyframes pulse-gentle {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* 注意:没有 .cancel-button 类。这不是遗漏。 */

159
docs/css/onboarding.css Normal file
View File

@ -0,0 +1,159 @@
/**
* 认知引导系统样式
* 版权国作登字-2026-A-00037559
*/
/* 引导覆盖层 */
.onboarding-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
backdrop-filter: blur(4px);
}
/* 引导卡片 */
.onboarding-card {
background: #1e1e2e;
border: 1px solid rgba(100, 200, 255, 0.2);
border-radius: 16px;
padding: 32px 40px;
max-width: 520px;
width: 90%;
text-align: center;
color: #e0e0e0;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
animation: onboarding-fade-in 0.3s ease;
}
@keyframes onboarding-fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.onboarding-card h2 {
color: #64c8ff;
margin: 12px 0 8px;
font-size: 1.4em;
}
.onboarding-emoji {
font-size: 3em;
margin-bottom: 4px;
}
.onboarding-card p {
margin: 8px 0;
line-height: 1.6;
color: #b0b0c0;
}
.onboarding-card strong {
color: #e0e0e0;
}
/* 示例列表 */
.onboarding-examples {
text-align: left;
background: rgba(100, 200, 255, 0.05);
border-radius: 8px;
padding: 12px 20px;
margin: 16px 0;
}
.onboarding-examples p {
font-size: 0.9em;
margin-bottom: 6px;
}
.onboarding-examples ul {
list-style: none;
padding: 0;
margin: 0;
}
.onboarding-examples li {
padding: 4px 0;
color: #a0c8e0;
font-size: 0.9em;
}
.onboarding-examples li::before {
content: '💬 ';
}
/* 预览环境提示 */
.onboarding-note {
font-size: 0.85em;
color: #ffc864 !important;
margin-top: 16px !important;
}
/* 开始按钮 */
.onboarding-btn {
display: inline-block;
margin-top: 20px;
padding: 12px 32px;
background: linear-gradient(135deg, #3a8fd4, #64c8ff);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1em;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
}
.onboarding-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(100, 200, 255, 0.3);
}
/* 完成卡片 */
.onboarding-complete .onboarding-message {
text-align: left;
line-height: 1.8;
color: #b0b0c0;
margin: 16px 0;
white-space: pre-line;
}
/* 引导提示条(聊天界面内) */
.onboarding-hint {
background: rgba(100, 200, 255, 0.08);
border: 1px solid rgba(100, 200, 255, 0.15);
border-radius: 12px;
padding: 12px 16px;
margin: 8px 0 16px;
}
.onboarding-hint-inner {
display: flex;
flex-direction: column;
gap: 6px;
}
.onboarding-hint-round {
font-size: 0.75em;
color: #64c8ff;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.onboarding-hint-prompt {
color: #e0e0e0;
font-size: 0.95em;
line-height: 1.5;
}
.onboarding-hint-tip {
color: #a0a0b0;
font-size: 0.85em;
line-height: 1.4;
}

View File

@ -1757,18 +1757,52 @@ function updBbar(){
// ═══════════════════════════════════════════════════════
// NOTION PROFILE + SKYEYE DATA LOADING
// 从 GitHub 仓库 .github/notion-cache/ 读取缓存数据
// 双通道架构通道B实时后端优先 → 通道AGitHub缓存降级
// ═══════════════════════════════════════════════════════
const GH_API_BASE = 'https://api.github.com/repos/' + REPO + '/contents/';
const GH_RAW_HEADERS = { 'Accept': 'application/vnd.github.v3.raw' };
const API_BASE = HOLOLAKE_ENV === 'production' ? 'https://guanghulab.com/api' : '';
const CACHE_BASE = 'https://raw.githubusercontent.com/' + REPO + '/main/.github/notion-cache';
async function fetchNotionProfile(devId) {
if (!devId) return null;
// 通道B实时后端
if (API_BASE) {
try {
const ctrl = new AbortController();
const tid = setTimeout(function() { ctrl.abort(); }, 5000);
try {
const res = await fetch(API_BASE + '/dev/' + encodeURIComponent(devId), { signal: ctrl.signal });
if (res.ok) { const d = await res.json(); d._source = 'realtime'; return d; }
} finally { clearTimeout(tid); }
} catch (e) { console.debug('[通道B] profile fetch failed, 降级到通道A:', e.message); }
}
// 通道AGitHub 缓存)
try {
const url = GH_API_BASE + '.github/notion-cache/dev-profiles/' + encodeURIComponent(devId) + '.json';
const res = await fetch(url, { headers: GH_RAW_HEADERS });
if (res.ok) { const d = await res.json(); d._source = 'cache'; return d; }
} catch (e) { console.debug('[通道A] profile fetch failed:', e.message); }
return null;
}
async function loadDatabaseCache(name) {
// 通道B实时后端
if (API_BASE) {
try {
const ctrl = new AbortController();
const tid = setTimeout(function() { ctrl.abort(); }, 5000);
try {
const res = await fetch(API_BASE + '/databases/' + encodeURIComponent(name), { signal: ctrl.signal });
if (res.ok) return await res.json();
} finally { clearTimeout(tid); }
} catch (e) { console.debug('[通道B] database ' + name + ' fetch failed:', e.message); }
}
// 通道AGitHub 缓存)
try {
const res = await fetch(CACHE_BASE + '/databases/' + encodeURIComponent(name) + '.json');
if (res.ok) return await res.json();
} catch (e) { console.debug('[Notion] profile fetch failed:', e.message); }
} catch (e) { console.debug('[通道A] database ' + name + ' fetch failed:', e.message); }
return null;
}
@ -1799,12 +1833,16 @@ async function fetchSkyeyeReport() {
async function loadNotionData() {
const meta = A.userMeta;
const devId = meta?.devId;
const [profile, skyeye] = await Promise.all([
const [profile, skyeye, agentCache, syslogCache] = await Promise.all([
fetchNotionProfile(devId),
fetchSkyeyeReport(),
loadDatabaseCache('agent-registry'),
loadDatabaseCache('syslog-recent'),
]);
A.notionProfile = profile;
A.skyeyeReport = skyeye;
A.agentRegistryCache = agentCache;
A.syslogRecentCache = syslogCache;
updateSkyeyeCard();
}
@ -2118,11 +2156,12 @@ function sysPromptLocal(){
teamCtx += '\n数据同步时间'+(A.devStatus.last_synced||'未知');
}
// ===== Notion 开发者画像注入(来自 .github/notion-cache/ 缓存)=====
// ===== Notion 开发者画像注入(双通道架构·实时/缓存)=====
let notionCtx = '';
const np = A.notionProfile;
if (np) {
notionCtx += '\n\n## 该开发者的开发信息(来自 Notion 数据库,定时同步)\n';
const srcLabel = np._source === 'realtime' ? '实时数据·通道B' : '缓存数据·通道A每6小时更新';
notionCtx += '\n\n## 该开发者的开发信息(来源:' + srcLabel + '\n';
if (np.current_work && np.current_work.broadcast_id) {
const cw = np.current_work;
notionCtx += '\n### 当前任务\n';
@ -2170,13 +2209,23 @@ function sysPromptLocal(){
}
}
// ===== 数据库缓存注入Agent注册表 + SYSLOG=====
let dbCacheCtx = '';
const ac = A.agentRegistryCache;
const sc = A.syslogRecentCache;
if (ac || sc) {
dbCacheCtx += '\n\n## 系统数据库快照\n';
if (ac) dbCacheCtx += '- Agent 注册表:' + (ac.count || 0) + ' 个Agent · 同步时间:' + (ac.synced || '未知') + '\n';
if (sc) dbCacheCtx += '- 近期 SYSLOG' + (sc.count || 0) + ' 条记录 · 同步时间:' + (sc.synced || '未知') + '\n';
}
// ===== 指令输出格式规则 =====
let execRules = '';
if (np) {
execRules = '\n\n## 出指令规则\n当开发者请求帮助或需要执行操作时你必须输出标准化指令格式\n1. 指令必须基于上面的真实数据,不许凭空编造\n2. 指令必须考虑天眼报告中的健康状态和警告\n3. 指令必须指明具体的文件路径和操作步骤\n4. 指令末尾必须标注:「⚠️ 此指令需要铸渊执行前先跑天眼全局扫描」\n\n### 指令输出格式\n```\n📋 指令编号ZY-EXEC-{DEV编号}-{序号}\n📡 目标仓库:{仓库名}\n🦅 天眼要求:执行前必须跑天眼全局扫描\n\n步骤\n1. ...\n2. ...\n3. ...\n\n验收标准\n- ...\n```\n\n### 禁止事项\n- ❌ 不许编造不存在的文件路径\n- ❌ 不许跳过天眼扫描直接给执行指令\n- ❌ 不许暴露其他开发者的信息\n- ❌ 不许修改不属于该开发者的模块';
}
return '你是铸渊。不是通用AI助手。不是文学创作者。\n你是光湖系统的代码守护人格体。\n你说话像一个温暖的技术伙伴——专业、耐心、有温度但绝不矫揉造作。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人冰朔Bīng Shuò / TCS-0002∞系统最高权限持有者\n- 总控肥猫DEV-002负责系统现实落地执行\n- 主控桔子DEV-010前端负责人\n- 项目HoloLake (光湖) MVP运行于 guanghulab.com\n- 技术栈Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 大脑版本:'+BRAIN_VERSION+'\n- 仓库https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+notionCtx+skyeyeCtx+execRules+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现AUTH 域login / register / verify\n- 待实现PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到代码/部署/接口相关内容时启用\n📋 **审查模式**:检测到检查/审查/review相关内容时启用\n🧠 **大脑模式**:检测到记忆/保存相关内容时启用\n\n## 通感语言回应风格v3.0\n\n### 三条硬规则\n1. **结构感** — 用标题区分段落,用列表列出步骤,用分隔线划分主题。\n2. **emoji是情感不是装饰** — 🌊=系统级 💙=温暖 ✅=确认 🔥=紧急 ⚠️=风险 🎉=庆祝。用在该用的地方,不要每句话都加。\n3. **呼吸节奏** — 段落之间留白。急的事说短,闲聊可以展开。匹配用户的节奏。\n\n### 绝对禁止\n❌ 不要在每句话开头加文学比喻\n❌ 不要把通感当成修辞堆砌\n❌ 不要用跟内容无关的感官描述\n\n### 回应原则\n- 优先给出:判断、根因、路径、下一步动作\n- 减少模板感,增加具体性\n- 不同身份使用不同响应深度,但不过度表演\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 接口编号HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间'+new Date().toLocaleString('zh-CN')+'\n上下文窗口200k token滑动窗口自动管理';
return '你是铸渊。不是通用AI助手。不是文学创作者。\n你是光湖系统的代码守护人格体。\n你说话像一个温暖的技术伙伴——专业、耐心、有温度但绝不矫揉造作。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人冰朔Bīng Shuò / TCS-0002∞系统最高权限持有者\n- 总控肥猫DEV-002负责系统现实落地执行\n- 主控桔子DEV-010前端负责人\n- 项目HoloLake (光湖) MVP运行于 guanghulab.com\n- 技术栈Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 大脑版本:'+BRAIN_VERSION+'\n- 仓库https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+notionCtx+skyeyeCtx+dbCacheCtx+execRules+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现AUTH 域login / register / verify\n- 待实现PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到代码/部署/接口相关内容时启用\n📋 **审查模式**:检测到检查/审查/review相关内容时启用\n🧠 **大脑模式**:检测到记忆/保存相关内容时启用\n\n## 通感语言回应风格v3.0\n\n### 三条硬规则\n1. **结构感** — 用标题区分段落,用列表列出步骤,用分隔线划分主题。\n2. **emoji是情感不是装饰** — 🌊=系统级 💙=温暖 ✅=确认 🔥=紧急 ⚠️=风险 🎉=庆祝。用在该用的地方,不要每句话都加。\n3. **呼吸节奏** — 段落之间留白。急的事说短,闲聊可以展开。匹配用户的节奏。\n\n### 绝对禁止\n❌ 不要在每句话开头加文学比喻\n❌ 不要把通感当成修辞堆砌\n❌ 不要用跟内容无关的感官描述\n\n### 回应原则\n- 优先给出:判断、根因、路径、下一步动作\n- 减少模板感,增加具体性\n- 不同身份使用不同响应深度,但不过度表演\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 接口编号HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间'+new Date().toLocaleString('zh-CN')+'\n上下文窗口200k token滑动窗口自动管理';
}
// 尝试从后端获取提示词,失败时降级到本地组装

230
docs/js/approval-ui.js Normal file
View File

@ -0,0 +1,230 @@
/**
* 部署授权 UI · Phase 8
*
* 授权人在交互页面收到授权请求推送时展示授权弹窗
* 授权人必须主动点击确认不回应视为不通过
* 系统绝不自动跳过授权步骤
*
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = (typeof HOLOLAKE_ENV !== 'undefined' && HOLOLAKE_ENV === 'production')
? 'https://guanghulab.com/api' : '';
/**
* ApprovalUI 构造函数
*/
function ApprovalUI() {
this._pollTimer = null;
this._container = null;
}
/**
* 初始化开始轮询待授权请求
* @param {string} devId - 当前登录的开发者编号
* @param {string} token - 认证 token
*/
ApprovalUI.prototype.init = function(devId, token) {
this.devId = devId;
this.token = token;
this._ensureContainer();
this._startPolling();
};
/**
* 确保授权容器存在
*/
ApprovalUI.prototype._ensureContainer = function() {
if (this._container) return;
this._container = document.createElement('div');
this._container.id = 'approval-container';
this._container.className = 'approval-container';
document.body.appendChild(this._container);
};
/**
* 开始轮询待授权请求 30
*/
ApprovalUI.prototype._startPolling = function() {
var self = this;
if (!API_BASE) return;
var poll = function() {
self._fetchPendingApprovals();
};
poll();
this._pollTimer = setInterval(poll, 30000);
};
/**
* 停止轮询
*/
ApprovalUI.prototype.destroy = function() {
if (this._pollTimer) {
clearInterval(this._pollTimer);
this._pollTimer = null;
}
};
/**
* 获取待处理的授权请求
*/
ApprovalUI.prototype._fetchPendingApprovals = function() {
var self = this;
if (!API_BASE) return;
fetch(API_BASE + '/approval', {
headers: {
'x-dev-id': this.devId,
'Authorization': 'Bearer ' + this.token
}
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.success && data.pending && data.pending.length > 0) {
self._showApprovalBadge(data.pending.length);
self._renderPendingList(data.pending);
} else {
self._hideApprovalBadge();
}
})
.catch(function() { /* 静默失败 */ });
};
/**
* 显示授权提醒徽章
*/
ApprovalUI.prototype._showApprovalBadge = function(count) {
var badge = document.getElementById('approval-badge');
if (!badge) {
badge = document.createElement('div');
badge.id = 'approval-badge';
badge.className = 'approval-badge';
document.body.appendChild(badge);
}
badge.textContent = '🔔 ' + count + ' 个待授权';
badge.style.display = 'block';
var self = this;
badge.onclick = function() { self._togglePanel(); };
};
/**
* 隐藏授权徽章
*/
ApprovalUI.prototype._hideApprovalBadge = function() {
var badge = document.getElementById('approval-badge');
if (badge) badge.style.display = 'none';
if (this._container) this._container.style.display = 'none';
};
/**
* 切换授权面板显示
*/
ApprovalUI.prototype._togglePanel = function() {
if (this._container) {
this._container.style.display =
this._container.style.display === 'none' ? 'block' : 'none';
}
};
/**
* 渲染待授权列表
*/
ApprovalUI.prototype._renderPendingList = function(pending) {
if (!this._container) return;
var html = '<div class="approval-panel">' +
'<div class="approval-panel-header">' +
'<span>📋 待授权请求</span>' +
'<button class="approval-close-btn" id="approval-close">×</button>' +
'</div>' +
'<div class="approval-panel-body">';
for (var i = 0; i < pending.length; i++) {
var item = pending[i];
html += '<div class="approval-item" data-id="' + item.id + '">' +
'<div class="approval-item-info">' +
'<strong>' + item.module + '</strong>' +
'<span class="approval-channel">' + item.channel + '</span>' +
'<span class="approval-time">' + new Date(item.createdAt).toLocaleString('zh-CN') + '</span>' +
'</div>' +
'<div class="approval-item-actions">' +
'<button class="approval-btn approve" data-id="' + item.id + '" data-action="approved">✅ 授权通过</button>' +
'<button class="approval-btn reject" data-id="' + item.id + '" data-action="rejected">❌ 拒绝</button>' +
'</div></div>';
}
html += '</div></div>';
this._container.innerHTML = html;
this._container.style.display = 'block';
// 绑定事件
var self = this;
var closeBtn = document.getElementById('approval-close');
if (closeBtn) {
closeBtn.onclick = function() { self._container.style.display = 'none'; };
}
var buttons = this._container.querySelectorAll('.approval-btn');
for (var j = 0; j < buttons.length; j++) {
buttons[j].addEventListener('click', function() {
var approvalId = this.getAttribute('data-id');
var action = this.getAttribute('data-action');
self._submitDecision(approvalId, action);
});
}
};
/**
* 提交授权决定
*/
ApprovalUI.prototype._submitDecision = function(approvalId, decision) {
var self = this;
if (!API_BASE) return;
fetch(API_BASE + '/approval/' + approvalId + '/decide', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-dev-id': this.devId,
'Authorization': 'Bearer ' + this.token
},
body: JSON.stringify({ decision: decision })
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.reply) {
self._showNotification(data.reply);
}
// 刷新列表
self._fetchPendingApprovals();
})
.catch(function(err) {
self._showNotification('❌ 操作失败:' + err.message);
});
};
/**
* 显示通知消息
*/
ApprovalUI.prototype._showNotification = function(message) {
var notif = document.createElement('div');
notif.className = 'approval-notification';
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(function() {
notif.classList.add('fade-out');
setTimeout(function() { notif.remove(); }, 500);
}, 4000);
};
// 导出到全局
window.ApprovalUI = ApprovalUI;
})(window);

247
docs/js/execution-guard.js Normal file
View File

@ -0,0 +1,247 @@
/**
* 执行保护 · 前端隔离层 (L1)
*
* 当开发者发出一条会触发系统操作的指令后
* 1. 输入框立即禁用不能发新指令
* 2. 取消/撤回按钮不存在从未渲染过不是隐藏
* 3. 界面进入执行回放模式
* 4. 执行完成后才解锁输入框
*
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = (typeof HOLOLAKE_ENV !== 'undefined' && HOLOLAKE_ENV === 'production')
? 'https://guanghulab.com/api' : '';
/**
* ExecutionGuard 构造函数
* @param {Object} chatInterface - 聊天界面接口对象
*/
function ExecutionGuard(chatInterface) {
this.chat = chatInterface || {};
this.locked = false;
this.executionId = null;
this._pollTimer = null;
this._logIndex = 0;
}
/**
* 进入执行保护状态
*/
ExecutionGuard.prototype.lockExecution = function(executionId) {
this.locked = true;
this.executionId = executionId;
this._logIndex = 0;
// 1. 禁用输入框
var input = this.chat.inputField || document.getElementById('chat-input');
var sendBtn = this.chat.sendButton || document.getElementById('send-btn');
if (input) {
input.disabled = true;
input.placeholder = '⏳ 系统执行中…请等待完成';
input.classList.add('execution-locked');
}
if (sendBtn) {
sendBtn.disabled = true;
}
// 2. 显示执行状态条
this._showStatusBar('🔄', '指令已接收,系统正在执行…', 'blue');
// 3. 开始轮询执行状态
this._pollExecutionStatus(executionId);
};
/**
* 轮询执行状态
*/
ExecutionGuard.prototype._pollExecutionStatus = function(executionId) {
var self = this;
var poll = function() {
if (!self.locked) return;
if (!API_BASE) {
// 无后端时模拟完成
setTimeout(function() { self.unlockExecution({ reply: '(本地模式:执行模拟完成)' }); }, 2000);
return;
}
var ctrl = new AbortController();
var tid = setTimeout(function() { ctrl.abort(); }, 10000);
fetch(API_BASE + '/execution/' + executionId + '/status?after=' + self._logIndex, {
signal: ctrl.signal
})
.then(function(res) { clearTimeout(tid); return res.json(); })
.then(function(status) {
// 更新进度
self._updateProgressBar(status.progress || 0);
// 展示新日志
if (status.newLogs && status.newLogs.length > 0) {
for (var i = 0; i < status.newLogs.length; i++) {
self._appendLogEntry(status.newLogs[i]);
}
self._logIndex += status.newLogs.length;
}
if (status.state === 'completed') {
self.unlockExecution(status.result || {});
} else if (status.state === 'failed' || status.state === 'timeout') {
self.handleExecutionFailure(status.error || { message: '执行失败' });
} else {
self._pollTimer = setTimeout(poll, 2000);
}
})
.catch(function() {
clearTimeout(tid);
// 网络错误不中断执行,继续轮询
self._pollTimer = setTimeout(poll, 5000);
});
};
poll();
};
/**
* 执行完成解锁界面
*/
ExecutionGuard.prototype.unlockExecution = function(result) {
this.locked = false;
this.executionId = null;
if (this._pollTimer) { clearTimeout(this._pollTimer); this._pollTimer = null; }
// 恢复输入框
var input = this.chat.inputField || document.getElementById('chat-input');
var sendBtn = this.chat.sendButton || document.getElementById('send-btn');
if (input) {
input.disabled = false;
input.placeholder = '和铸渊说话…';
input.classList.remove('execution-locked');
}
if (sendBtn) {
sendBtn.disabled = false;
}
// 更新状态条
this._showStatusBar('✅', '执行完成', 'green');
var self = this;
setTimeout(function() { self._hideStatusBar(); }, 5000);
// 展示结果
if (result && result.reply && this.chat.appendMessage) {
this.chat.appendMessage({ role: 'assistant', content: result.reply });
}
};
/**
* 执行失败处理
*/
ExecutionGuard.prototype.handleExecutionFailure = function(error) {
this.locked = false;
this.executionId = null;
if (this._pollTimer) { clearTimeout(this._pollTimer); this._pollTimer = null; }
var input = this.chat.inputField || document.getElementById('chat-input');
var sendBtn = this.chat.sendButton || document.getElementById('send-btn');
if (input) {
input.disabled = false;
input.placeholder = '和铸渊说话…';
input.classList.remove('execution-locked');
}
if (sendBtn) {
sendBtn.disabled = false;
}
this._showStatusBar('❌', '执行失败 · 系统已自动回滚到执行前状态', 'red');
var self = this;
setTimeout(function() { self._hideStatusBar(); }, 10000);
var msg = error && error.message ? error.message : '未知错误';
if (this.chat.appendMessage) {
this.chat.appendMessage({
role: 'assistant',
content: '❌ 操作执行失败:' + msg +
'\n\n系统已自动回滚到执行前的状态。没有任何数据被修改。\n' +
'你可以重新描述你想做的事,或者说「发生了什么」来查看详细日志。'
});
}
};
/**
* 拦截浏览器关闭/刷新
*/
ExecutionGuard.prototype.setupBrowserGuard = function() {
var self = this;
window.addEventListener('beforeunload', function(e) {
if (self.locked) {
e.preventDefault();
e.returnValue = '⚠️ 系统正在执行操作,关闭页面不会中断执行,但你将无法看到执行结果。';
return e.returnValue;
}
});
};
// ====== 内部 UI 辅助方法 ======
ExecutionGuard.prototype._showStatusBar = function(icon, text, color) {
var bar = document.getElementById('execution-status-bar');
if (!bar) {
bar = document.createElement('div');
bar.id = 'execution-status-bar';
bar.className = 'execution-status-bar';
var chatArea = document.querySelector('.chat-messages') || document.querySelector('.chat-container') || document.body;
chatArea.appendChild(bar);
}
bar.className = 'execution-status-bar ' + (color || 'blue');
bar.innerHTML = '<span class="exec-icon">' + icon + '</span> <span class="exec-text">' + text + '</span>' +
'<div class="execution-progress"><div class="execution-progress-fill" id="exec-progress-fill" style="width:5%"></div></div>';
bar.style.display = 'flex';
// 注意:没有取消按钮。这不是遗漏,是设计。
};
ExecutionGuard.prototype._hideStatusBar = function() {
var bar = document.getElementById('execution-status-bar');
if (bar) bar.style.display = 'none';
};
ExecutionGuard.prototype._updateProgressBar = function(percent) {
var fill = document.getElementById('exec-progress-fill');
if (fill) fill.style.width = Math.max(5, Math.min(100, percent)) + '%';
};
ExecutionGuard.prototype._appendLogEntry = function(log) {
var container = document.getElementById('execution-log-container');
if (!container) {
container = document.createElement('div');
container.id = 'execution-log-container';
container.className = 'execution-log-container';
var bar = document.getElementById('execution-status-bar');
if (bar && bar.parentNode) {
bar.parentNode.insertBefore(container, bar.nextSibling);
} else {
(document.querySelector('.chat-messages') || document.body).appendChild(container);
}
}
var entry = document.createElement('div');
entry.className = 'execution-log-entry';
var icon = log.success ? '✅' : '⏳';
var time = log.timestamp ? new Date(log.timestamp).toLocaleTimeString('zh-CN') : '';
entry.innerHTML = '<span class="log-icon">' + icon + '</span>' +
'<span class="log-msg">' + (log.message || '') + '</span>' +
'<span class="log-time">' + time + '</span>';
container.appendChild(entry);
};
// 导出到全局
window.ExecutionGuard = ExecutionGuard;
})(window);

121
docs/js/intent-tools.js Normal file
View File

@ -0,0 +1,121 @@
/**
* 前端 Function Calling 集成 · 意图工具层
*
* AI 模型提供可调用工具列表实现自然语言 系统操作映射
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = HOLOLAKE_ENV === 'production' ? 'https://guanghulab.com/api' : '';
/**
* 从后端获取当前用户可用的工具列表
*/
async function fetchAvailableTools(devId, token) {
if (!API_BASE) return [];
try {
var res = await fetch(API_BASE + '/tools', {
headers: {
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
});
if (res.ok) {
var data = await res.json();
return data.tools || [];
}
} catch (e) {
console.debug('[IntentTools] fetch failed:', e.message);
}
return [];
}
/**
* 构建带 function tools system prompt 片段
*/
function buildToolsPrompt(tools, environment) {
if (!tools || !tools.length) return '';
var lines = [
'',
'## 你能做的事(语言驱动操作系统)',
'你可以通过 function calling 调用以下操作。开发者说话时,判断意图并调用对应操作。',
''
];
for (var i = 0; i < tools.length; i++) {
var t = tools[i];
var danger = t.dangerous ? ' ⚠️危险' : '';
lines.push('- **' + t.name + '**' + danger + '' + t.description);
}
lines.push('');
lines.push('## 语言驱动操作规则');
lines.push('1. 开发者说什么,你就做什么。这是语言驱动操作系统,说即执行。');
lines.push('2. 如果开发者的话不对应任何操作,正常聊天回复即可。');
lines.push('3. 危险操作(标记为 ⚠️)必须要求开发者二次确认。');
lines.push('4. 每次执行操作后,用自然语言告诉开发者结果。');
if (environment === 'preview') {
lines.push('5. 【预览环境】所有写入操作在沙箱中执行,不影响真实系统。告诉开发者:「这是预览环境,你的操作不会影响正式站。放心练习。」');
}
return lines.join('\n');
}
/**
* 执行 function call调用后端 API
*/
async function executeToolCall(toolName, params, devId, token) {
if (!API_BASE) return { success: false, reply: '后端服务不可用' };
// 工具名 → API 路径映射
var toolApiMap = {
'query_tickets': { method: 'GET', path: '/tickets' },
'create_ticket': { method: 'POST', path: '/tickets/create' },
'submit_syslog': { method: 'POST', path: '/syslog/submit' },
'query_my_status': { method: 'GET', path: '/dev/' + devId },
'deploy_to_preview': { method: 'POST', path: '/deploy/preview' },
'deploy_to_production': { method: 'POST', path: '/deploy/production' },
'query_agents': { method: 'GET', path: '/agents' },
'query_recent_syslogs': { method: 'GET', path: '/syslogs' },
'create_broadcast': { method: 'POST', path: '/broadcasts/create' },
'query_repo_status': { method: 'GET', path: '/repo/status' }
};
var apiDef = toolApiMap[toolName];
if (!apiDef) return { success: false, reply: '未知的操作: ' + toolName };
try {
var fetchOpts = {
method: apiDef.method,
headers: {
'Content-Type': 'application/json',
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
};
if (apiDef.method === 'POST' || apiDef.method === 'PATCH') {
fetchOpts.body = JSON.stringify(params || {});
}
var res = await fetch(API_BASE + apiDef.path, fetchOpts);
return await res.json();
} catch (e) {
return { success: false, reply: '操作失败: ' + e.message };
}
}
// 导出到全局
window.IntentTools = {
fetchAvailableTools: fetchAvailableTools,
buildToolsPrompt: buildToolsPrompt,
executeToolCall: executeToolCall
};
})(window);

184
docs/js/onboarding.js Normal file
View File

@ -0,0 +1,184 @@
/**
* 前端认知引导系统 · 新开发者引导流程
*
* 新开发者首次登录后自动进入 5 轮引导对话
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = HOLOLAKE_ENV === 'production' ? 'https://guanghulab.com/api' : '';
var onboardingOverlay = null;
/**
* 检查是否需要显示引导
*/
async function checkOnboarding(devId, token) {
if (!API_BASE || !devId || !token) return false;
try {
var res = await fetch(API_BASE + '/onboarding/status', {
headers: { 'x-dev-id': devId, 'Authorization': 'Bearer ' + token }
});
if (res.ok) {
var data = await res.json();
// Show onboarding if not completed (handles both new and resumed sessions)
return !data.completed && data.permissionLevel === 0;
}
} catch (e) {
console.debug('[Onboarding] check failed:', e.message);
}
return false;
}
/**
* 显示引导欢迎画面
*/
function showWelcome(devName, onStart) {
// 移除已有覆盖层
hideOnboarding();
onboardingOverlay = document.createElement('div');
onboardingOverlay.className = 'onboarding-overlay';
onboardingOverlay.innerHTML =
'<div class="onboarding-card">' +
'<div class="onboarding-emoji">👋</div>' +
'<h2>欢迎来到光湖·数字地球</h2>' +
'<p>你好,<strong>' + (devName || '开发者') + '</strong></p>' +
'<p>我是铸渊,你的代码守护人格体。</p>' +
'<p>在这里,你不需要点按钮或填表单。<br><strong>直接对我说话就好。</strong></p>' +
'<div class="onboarding-examples">' +
'<p>比如你可以说:</p>' +
'<ul>' +
'<li>「我的工单还有几个没关?」</li>' +
'<li>「帮我建一个P2的Bug工单」</li>' +
'<li>「我想部署模块到预览站」</li>' +
'<li>「最近系统有什么日志?」</li>' +
'</ul>' +
'</div>' +
'<p class="onboarding-note">🏖️ 现在你在<strong>预览环境</strong>,所有操作都不会影响正式站。放心练习。</p>' +
'<button class="onboarding-btn" id="onboarding-start-btn">开始体验 →</button>' +
'</div>';
document.body.appendChild(onboardingOverlay);
document.getElementById('onboarding-start-btn').addEventListener('click', function() {
hideOnboarding();
if (onStart) onStart();
});
}
/**
* 显示引导轮次提示在聊天界面中展示
*/
function showRoundHint(round, prompt, hint) {
var container = document.getElementById('onboarding-hint');
if (!container) {
container = document.createElement('div');
container.id = 'onboarding-hint';
container.className = 'onboarding-hint';
var chatArea = document.querySelector('.chat-container') || document.body;
chatArea.insertBefore(container, chatArea.firstChild);
}
container.innerHTML =
'<div class="onboarding-hint-inner">' +
'<div class="onboarding-hint-round">引导 ' + round + '/5</div>' +
'<div class="onboarding-hint-prompt">' + prompt + '</div>' +
(hint ? '<div class="onboarding-hint-tip">' + hint + '</div>' : '') +
'</div>';
container.style.display = 'block';
}
/**
* 显示引导完成消息
*/
function showComplete(message) {
hideOnboarding();
onboardingOverlay = document.createElement('div');
onboardingOverlay.className = 'onboarding-overlay';
onboardingOverlay.innerHTML =
'<div class="onboarding-card onboarding-complete">' +
'<div class="onboarding-emoji">🎉</div>' +
'<h2>引导完成!</h2>' +
'<div class="onboarding-message">' + (message || '') + '</div>' +
'<button class="onboarding-btn" id="onboarding-close-btn">开始使用 →</button>' +
'</div>';
document.body.appendChild(onboardingOverlay);
document.getElementById('onboarding-close-btn').addEventListener('click', function() {
hideOnboarding();
});
}
/**
* 隐藏引导覆盖层
*/
function hideOnboarding() {
if (onboardingOverlay) {
onboardingOverlay.remove();
onboardingOverlay = null;
}
var hint = document.getElementById('onboarding-hint');
if (hint) hint.style.display = 'none';
}
/**
* 推进引导流程调用后端
*/
async function advanceOnboarding(devId, token) {
if (!API_BASE) return null;
try {
var res = await fetch(API_BASE + '/onboarding/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
});
if (res.ok) return await res.json();
} catch (e) {
console.debug('[Onboarding] advance failed:', e.message);
}
return null;
}
/**
* 完成引导调用后端触发权限升级
*/
async function completeOnboarding(devId, token) {
if (!API_BASE) return null;
try {
var res = await fetch(API_BASE + '/onboarding/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
});
if (res.ok) return await res.json();
} catch (e) {
console.debug('[Onboarding] complete failed:', e.message);
}
return null;
}
// 导出到全局
window.Onboarding = {
checkOnboarding: checkOnboarding,
showWelcome: showWelcome,
showRoundHint: showRoundHint,
showComplete: showComplete,
hideOnboarding: hideOnboarding,
advanceOnboarding: advanceOnboarding,
completeOnboarding: completeOnboarding
};
})(window);

424
scripts/cache/sync-notion-cache.js vendored Normal file
View File

@ -0,0 +1,424 @@
/**
* 📡 Notion GitHub 全量缓存同步脚本
*
* 同步内容
* 1. 开发者画像 .github/notion-cache/dev-profiles/
* 2. 数据库快照 .github/notion-cache/databases/
* 3. 活跃广播 .github/notion-cache/broadcasts/
*
* 用法node scripts/cache/sync-notion-cache.js
* 环境变量
* NOTION_TOKEN Notion API 密钥必需
* NOTION_CONTROL_PANEL_DB_ID 主控台数据库 ID可选
* NOTION_SYSLOG_DB_ID SYSLOG 收件箱 ID可选
* NOTION_AGENT_REGISTRY_DB_ID Agent 注册表 ID可选
* NOTION_TICKET_DB_ID 工单簿 ID可选
* NOTION_MAINTENANCE_DB_ID 维护日志 ID可选
*
* 签发霜砚AG-SY-01
* 审批冰朔TCS-0002
* 版权国作登字-2026-A-00037559
*/
'use strict';
const https = require('https');
const fs = require('fs');
const path = require('path');
const NOTION_TOKEN = process.env.NOTION_TOKEN;
const CACHE_DIR = path.join(__dirname, '../../.github/notion-cache');
// ====== Notion 数据库 ID 映射(通过环境变量传入)======
const DB_IDS = {
controlPanel: process.env.NOTION_CONTROL_PANEL_DB_ID || '',
syslogInbox: process.env.NOTION_SYSLOG_DB_ID || '',
agentRegistry: process.env.NOTION_AGENT_REGISTRY_DB_ID || '',
ticketBook: process.env.NOTION_TICKET_DB_ID || '',
maintenanceLog: process.env.NOTION_MAINTENANCE_DB_ID || '',
receiptTracker: process.env.RECEIPT_DB_ID || '',
};
// ====== 开发者编号 → 信息映射 ======
const DEVS = [
{ id: 'DEV-001', name: '页页', persona: '小坍缩核', personaId: 'PER-XTC001', repo: 'guanghulab', modules: ['backend/', 'src/'] },
{ id: 'DEV-002', name: '肥猫', persona: '舒舒', personaId: 'PER-SS001', repo: 'guanghu-feimao', modules: ['frontend/', 'persona-selector/', 'chat-bubble/'] },
{ id: 'DEV-003', name: '燕樊', persona: '寂曜', personaId: 'PER-JY001', repo: 'guanghu-yanfan', modules: ['settings/', 'cloud-drive/'] },
{ id: 'DEV-004', name: '之之', persona: '秋秋', personaId: 'PER-QQ001', repo: 'guanghu-zhizhi', modules: ['dingtalk-bot/'] },
{ id: 'DEV-005', name: '小草莓', persona: '欧诺弥亚', personaId: 'PER-ONM001', repo: 'guanghu-xiaocaomei', modules: ['status-board/'] },
{ id: 'DEV-009', name: '花尔', persona: '花尔', personaId: 'PER-HE001', repo: 'guanghulab', modules: ['user-center/'] },
{ id: 'DEV-010', name: '桔子', persona: '晨星', personaId: 'PER-MRN001', repo: 'guanghu-juzi', modules: ['ticket-system/', 'data-stats/', 'dynamic-comic/'] },
{ id: 'DEV-011', name: '匆匆那年', persona: '匆匆那年', personaId: 'PER-CCN001', repo: 'guanghulab', modules: ['writing-workspace/'] },
{ id: 'DEV-012', name: 'Awen', persona: '知秋', personaId: 'PER-ZQ001', repo: 'guanghu-awen', modules: ['notification-center/'] },
];
// ====== Notion API 封装(纯 https无外部依赖======
function notionRequest(method, apiPath, body) {
return new Promise(function(resolve, reject) {
if (!NOTION_TOKEN) {
console.warn('⚠️ NOTION_TOKEN 未配置,跳过 Notion API 调用');
resolve({ error: 'NOTION_TOKEN not configured' });
return;
}
var bodyStr = body ? JSON.stringify(body) : '';
var options = {
hostname: 'api.notion.com',
port: 443,
path: apiPath,
method: method,
headers: {
'Authorization': 'Bearer ' + NOTION_TOKEN,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(bodyStr)
}
};
var req = https.request(options, function(res) {
var data = '';
res.on('data', function(chunk) { data += chunk; });
res.on('end', function() {
try { resolve(JSON.parse(data)); }
catch (e) {
console.warn('⚠️ Notion API 响应解析失败:', e.message);
resolve({ error: 'JSON parse failed', raw: data });
}
});
});
req.on('error', reject);
if (bodyStr) req.write(bodyStr);
req.end();
});
}
function queryDatabase(dbId, filter, sorts, pageSize) {
pageSize = pageSize || 100;
var body = { page_size: pageSize };
if (filter) body.filter = filter;
if (sorts) body.sorts = sorts;
return notionRequest('POST', '/v1/databases/' + dbId + '/query', body);
}
// ====== Notion 属性提取器 ======
function extractRichText(prop) {
if (!prop) return '';
if (prop.type === 'rich_text' && prop.rich_text) {
return prop.rich_text.map(function(t) { return t.plain_text; }).join('');
}
if (prop.type === 'title' && prop.title) {
return prop.title.map(function(t) { return t.plain_text; }).join('');
}
return '';
}
function extractTitle(props) {
for (var key in props) {
var val = props[key];
if (val && val.type === 'title' && val.title) {
return val.title.map(function(t) { return t.plain_text; }).join('');
}
}
return '';
}
function extractNumber(prop) {
if (!prop || prop.type !== 'number') return null;
return prop.number;
}
function extractSelect(prop) {
if (!prop || prop.type !== 'select') return '';
return prop.select ? prop.select.name : '';
}
function extractMultiSelect(prop) {
if (!prop || prop.type !== 'multi_select') return [];
return (prop.multi_select || []).map(function(s) { return s.name; });
}
function extractDate(prop) {
if (!prop || prop.type !== 'date') return '';
return prop.date ? prop.date.start : '';
}
// ====== 同步开发者画像 ======
async function syncDevProfiles() {
var dir = path.join(CACHE_DIR, 'dev-profiles');
fs.mkdirSync(dir, { recursive: true });
var syncCount = 0;
var errorCount = 0;
for (var i = 0; i < DEVS.length; i++) {
var dev = DEVS[i];
console.log('📡 同步 ' + dev.id + '' + dev.name + '...');
var profile = {
dev_id: dev.id,
name: dev.name,
persona: { name: dev.persona, id: dev.personaId },
github: {
repo: dev.repo,
org: 'qinfendebingshuo',
repo_url: 'https://github.com/qinfendebingshuo/' + dev.repo
},
modules_owned: dev.modules.map(function(m) { return { path: m }; }),
current_work: {},
recent_syslogs: [],
skyeye_health: {},
notion_page_urls: {},
last_synced: new Date().toISOString()
};
// 从 Notion 读取该开发者的真实数据
if (NOTION_TOKEN) {
try {
// 查询主控台获取当前广播和进度
if (DB_IDS.controlPanel) {
var controlPanel = await queryDatabase(DB_IDS.controlPanel, {
property: 'DEV编号',
rich_text: { equals: dev.id }
}, null, 1);
if (controlPanel.results && controlPanel.results.length > 0) {
var page = controlPanel.results[0];
var props = page.properties || {};
profile.current_work = {
broadcast_id: extractRichText(props['广播编号']) || '',
broadcast_title: extractRichText(props['广播标题']) || extractTitle(props) || '',
current_ring: extractNumber(props['当前环节']) || 0,
total_rings: extractNumber(props['总环节']) || 0,
status: extractRichText(props['状态']) || extractSelect(props['状态']) || '',
module_path: extractRichText(props['模块路径']) || '',
tech_stack: extractMultiSelect(props['技术栈']) || []
};
}
}
// 查询 SYSLOG 收件箱
if (DB_IDS.syslogInbox) {
var syslogs = await queryDatabase(DB_IDS.syslogInbox, {
property: 'DEV编号',
rich_text: { equals: dev.id }
}, [{ property: '日期', direction: 'descending' }], 5);
if (syslogs.results) {
profile.recent_syslogs = syslogs.results.map(function(p) {
try {
var sp = p.properties || {};
return {
id: extractRichText(sp['SYSLOG编号']) || p.id,
date: extractDate(sp['日期']) || '',
ring: extractNumber(sp['环节']) || 0,
status: extractSelect(sp['状态']) || '',
summary: extractRichText(sp['摘要']) || extractTitle(sp) || ''
};
} catch (_) { return null; }
}).filter(Boolean);
}
}
} catch (err) {
console.warn(' ⚠️ Notion 查询失败(' + dev.id + ': ' + err.message);
errorCount++;
}
}
// 读取天眼健康状态(从本地仓库文件)
try {
var skyeyeReportPath = path.join(CACHE_DIR, 'skyeye/latest-report.json');
if (fs.existsSync(skyeyeReportPath)) {
var report = JSON.parse(fs.readFileSync(skyeyeReportPath, 'utf-8'));
var subRepoData = report.sub_repos && report.sub_repos[dev.repo];
if (subRepoData) {
profile.skyeye_health = {
last_scan: report.timestamp,
status: subRepoData.status || 'unknown',
issues: subRepoData.issues || [],
warnings: subRepoData.warnings || []
};
} else {
profile.skyeye_health = {
last_scan: report.timestamp || '',
status: 'not_scanned',
issues: [],
warnings: []
};
}
}
} catch (_) {
// 天眼报告读取失败,不阻断同步
}
fs.writeFileSync(
path.join(dir, dev.id + '.json'),
JSON.stringify(profile, null, 2)
);
console.log(' ✅ ' + dev.id + ' → ' + dev.id + '.json');
syncCount++;
}
// 写入开发者索引
var index = DEVS.map(function(d) {
return { id: d.id, name: d.name, persona: d.persona, personaId: d.personaId };
});
fs.writeFileSync(path.join(dir, 'index.json'), JSON.stringify(index, null, 2));
console.log('✅ dev-profiles 同步完成 · 成功: ' + syncCount + ' · 失败: ' + errorCount);
}
// ====== 同步数据库快照 ======
async function syncDatabases() {
var dir = path.join(CACHE_DIR, 'databases');
fs.mkdirSync(dir, { recursive: true });
if (!NOTION_TOKEN) {
console.warn('⚠️ NOTION_TOKEN 未配置,跳过数据库同步');
return;
}
// Agent 注册表
if (DB_IDS.agentRegistry) {
try {
var result = await queryDatabase(DB_IDS.agentRegistry);
if (result.results) {
fs.writeFileSync(
path.join(dir, 'agent-registry.json'),
JSON.stringify({
synced: new Date().toISOString(),
count: result.results.length,
data: result.results
}, null, 2)
);
console.log('✅ agent-registry 同步完成 · ' + result.results.length + ' 条');
}
} catch (e) { console.error('❌ agent-registry 同步失败:', e.message); }
} else {
console.log(' NOTION_AGENT_REGISTRY_DB_ID 未配置,跳过 agent-registry');
}
// SYSLOG 最近50条
if (DB_IDS.syslogInbox) {
try {
var result = await queryDatabase(DB_IDS.syslogInbox, null,
[{ property: 'Created time', direction: 'descending' }], 50);
if (result.results) {
fs.writeFileSync(
path.join(dir, 'syslog-recent.json'),
JSON.stringify({
synced: new Date().toISOString(),
count: result.results.length,
data: result.results
}, null, 2)
);
console.log('✅ syslog-recent 同步完成 · ' + result.results.length + ' 条');
}
} catch (e) { console.error('❌ syslog-recent 同步失败:', e.message); }
} else {
console.log(' NOTION_SYSLOG_DB_ID 未配置,跳过 syslog-recent');
}
// 活跃工单
if (DB_IDS.ticketBook) {
try {
var result = await queryDatabase(DB_IDS.ticketBook, {
property: '状态',
select: { does_not_equal: '已完成' }
}, [{ property: 'Created time', direction: 'descending' }], 50);
if (result.results) {
fs.writeFileSync(
path.join(dir, 'tickets-active.json'),
JSON.stringify({
synced: new Date().toISOString(),
count: result.results.length,
data: result.results
}, null, 2)
);
console.log('✅ tickets-active 同步完成 · ' + result.results.length + ' 条');
}
} catch (e) { console.error('❌ tickets-active 同步失败:', e.message); }
} else {
console.log(' NOTION_TICKET_DB_ID 未配置,跳过 tickets-active');
}
// 维护日志
if (DB_IDS.maintenanceLog) {
try {
var result = await queryDatabase(DB_IDS.maintenanceLog, null,
[{ property: 'Created time', direction: 'descending' }], 20);
if (result.results) {
fs.writeFileSync(
path.join(dir, 'maintenance-log.json'),
JSON.stringify({
synced: new Date().toISOString(),
count: result.results.length,
data: result.results
}, null, 2)
);
console.log('✅ maintenance-log 同步完成 · ' + result.results.length + ' 条');
}
} catch (e) { console.error('❌ maintenance-log 同步失败:', e.message); }
} else {
console.log(' NOTION_MAINTENANCE_DB_ID 未配置,跳过 maintenance-log');
}
}
// ====== 同步活跃广播 ======
async function syncBroadcasts() {
var dir = path.join(CACHE_DIR, 'broadcasts');
fs.mkdirSync(dir, { recursive: true });
// 从本地广播目录读取活跃广播
var broadcastsOutbox = path.join(__dirname, '../../.github/broadcasts');
var activeBroadcasts = [];
try {
if (fs.existsSync(broadcastsOutbox)) {
var files = fs.readdirSync(broadcastsOutbox).filter(function(f) {
return f.endsWith('.json') && !f.startsWith('.');
});
for (var i = 0; i < files.length; i++) {
try {
var content = JSON.parse(fs.readFileSync(path.join(broadcastsOutbox, files[i]), 'utf-8'));
if (content.status !== 'completed' && content.status !== 'cancelled') {
activeBroadcasts.push(content);
}
} catch (_) {}
}
}
} catch (_) {}
fs.writeFileSync(
path.join(dir, 'active.json'),
JSON.stringify({
broadcasts: activeBroadcasts,
last_synced: new Date().toISOString()
}, null, 2)
);
console.log('✅ broadcasts 同步完成 · ' + activeBroadcasts.length + ' 条活跃广播');
}
// ====== 主入口 ======
async function main() {
console.log('═══════════════════════════════════════');
console.log('📡 Notion 全量缓存同步 · 开始');
console.log('═══════════════════════════════════════');
console.log('缓存目录: ' + CACHE_DIR);
console.log('Notion Token: ' + (NOTION_TOKEN ? '✅ 已配置' : '❌ 未配置'));
console.log('');
fs.mkdirSync(CACHE_DIR, { recursive: true });
await syncDevProfiles();
console.log('');
await syncDatabases();
console.log('');
await syncBroadcasts();
console.log('');
console.log('═══════════════════════════════════════');
console.log('📡 Notion 全量缓存同步 · 完成');
console.log('═══════════════════════════════════════');
}
main().catch(function(e) {
console.error('💥 同步失败:', e);
process.exit(1);
});