feat: implement dual-channel architecture (Channel A cache + Channel B backend)

- Add scripts/cache/sync-notion-cache.js for comprehensive Notion sync
- Add .github/workflows/sync-notion-cache.yml (every 6h + manual trigger)
- Add .github/workflows/deploy-backend.yml for ECS auto-deploy
- Add missing dev profiles: DEV-009.json, DEV-011.json, index.json
- Add .github/notion-cache/databases/ directory
- Add backend/api-server/ with Express server (port 3001)
  - Routes: health, dev, databases, chat, receipt
  - Services: notion, github, cache
  - Config: database ID mappings
  - PM2 ecosystem config
- Update docs/index.html with dual-channel fallback:
  - Channel B (realtime) → Channel A (cache) degradation
  - loadDatabaseCache() for agent-registry, syslog-recent
  - Enhanced system prompt with data source indicators

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/a260ce18-279a-4490-af90-a90a7b1f46cd
This commit is contained in:
copilot-swe-agent[bot] 2026-03-25 08:10:58 +00:00
parent 96b6b219cd
commit df21bae395
28 changed files with 1723 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"

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,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,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,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, 1);
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, 1);
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, 1);
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,108 @@
/**
* /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-\d{3}$/.test(devId)) {
return res.status(400).json({ error: true, code: 'INVALID_DEV_ID', message: '无效的开发者编号格式' });
}
var devInfo = DEV_REGISTRY[devId];
if (!devInfo) {
return res.status(404).json({ error: true, code: 'DEV_NOT_FOUND', message: '开发者不存在' });
}
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,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,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,53 @@
/**
* 🔗 光湖后端中间层 · 通道B 入口
*
* 实时调用 Notion API / GitHub API为前端提供数据代理
* 只监听 127.0.0.1通过 Nginx 反向代理对外暴露
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
const express = require('express');
const cors = require('cors');
const app = express();
// CORS只允许自己的域名
app.use(cors({
origin: [
'https://guanghulab.com',
'http://guanghulab.com',
'https://www.guanghulab.com',
'https://qinfendebingshuo.github.io'
],
credentials: true
}));
app.use(express.json());
// 路由注册
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.get('/', function(_req, res) {
res.json({
status: 'ok',
service: 'guanghu-api-server',
version: '1.0.0',
channel: 'B',
description: '光湖后端中间层 · 实时 Notion/GitHub 数据代理'
});
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, '127.0.0.1', function() {
console.log('🔗 光湖后端中间层启动 · 端口 ' + PORT);
console.log(' 通道B · 实时 Notion API / GitHub API 代理');
console.log(' 监听地址127.0.0.1:' + PORT + '(仅本机访问)');
});

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,132 @@
/**
* 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;
}
module.exports = {
getRepoStatus: getRepoStatus,
getRecentCommits: getRecentCommits,
getWorkflowRuns: getWorkflowRuns
};

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
};

View File

@ -1757,18 +1757,50 @@ 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);
const res = await fetch(API_BASE + '/dev/' + encodeURIComponent(devId), { signal: ctrl.signal });
clearTimeout(tid);
if (res.ok) { const d = await res.json(); d._source = 'realtime'; return d; }
} 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);
const res = await fetch(API_BASE + '/databases/' + encodeURIComponent(name), { signal: ctrl.signal });
clearTimeout(tid);
if (res.ok) return await res.json();
} 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 +1831,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 +2154,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 +2207,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滑动窗口自动管理';
}
// 尝试从后端获取提示词,失败时降级到本地组装

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);
});