322 lines
13 KiB
YAML
322 lines
13 KiB
YAML
# ═══════════════════════════════════════════════
|
||
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
||
# 📜 Copyright: 国作登字-2026-A-00037559
|
||
# ═══════════════════════════════════════════════
|
||
name: Notion Callback Pipeline
|
||
# 📥 Notion → GitHub 回传闭环管道
|
||
#
|
||
# 当 Notion 侧核心大脑人格体处理完 SYSLOG 工单后,
|
||
# 通过 repository_dispatch 回传处理结果到 GitHub。
|
||
# 铸渊 Agent 接收回传,重新唤醒核心大脑认知进行二次核验,
|
||
# 确认新广播生成无误后,自动发送邮件通知开发者。
|
||
#
|
||
# 触发方式:
|
||
# Notion 侧 Agent 调用 GitHub API 发送 repository_dispatch 事件
|
||
# event_type: notion-syslog-callback
|
||
# client_payload: {
|
||
# broadcast_id: "BC-XXX-XXX-XX",
|
||
# dev_email: "developer@email.com",
|
||
# notion_result: "Notion 侧处理结果文本",
|
||
# ticket_id: "Notion 工单 ID",
|
||
# status: "approved" | "needs_revision"
|
||
# }
|
||
#
|
||
# 闭环流程:
|
||
# ① Notion 侧处理完成 → repository_dispatch 回传
|
||
# ② 铸渊 Agent 接收并解析回传数据
|
||
# ③ 重新唤醒铸渊核心大脑认知(二次核验)
|
||
# ④ 核心大脑核对广播生成无误
|
||
# ⑤ 铸渊 Agent 发送邮件通知开发者
|
||
# ⑥ 闭环结束
|
||
#
|
||
# 依赖 Secrets:
|
||
# LLM_API_KEY 第三方 LLM 平台密钥
|
||
# LLM_BASE_URL 第三方 LLM 平台 API 地址
|
||
# NOTION_API_TOKEN Notion API token
|
||
# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID
|
||
# PORTRAIT_DB_ID 开发者动态画像库数据库 ID
|
||
# FINGERPRINT_DB_ID 模块指纹注册表数据库 ID
|
||
# SMTP_USER QQ 邮箱地址
|
||
# SMTP_PASS QQ 邮箱 SMTP 授权码
|
||
|
||
on:
|
||
repository_dispatch:
|
||
types: [notion-syslog-callback]
|
||
|
||
jobs:
|
||
verify-and-notify:
|
||
name: 📥 Notion 回传 · 二次核验 · 邮件通知
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: write
|
||
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '20'
|
||
|
||
- name: Install dependencies
|
||
run: npm ci --ignore-scripts
|
||
|
||
- name: 📥 Parse Notion callback
|
||
id: callback
|
||
uses: actions/github-script@v7
|
||
with:
|
||
script: |
|
||
const payload = context.payload.client_payload || {};
|
||
|
||
const broadcastId = payload.broadcast_id || '';
|
||
const devEmail = payload.dev_email || '';
|
||
const notionResult = payload.notion_result || '';
|
||
const ticketId = payload.ticket_id || '';
|
||
const status = payload.status || 'unknown';
|
||
|
||
if (!broadcastId) {
|
||
core.setFailed('❌ 缺少 broadcast_id');
|
||
return;
|
||
}
|
||
|
||
core.setOutput('broadcast_id', broadcastId);
|
||
core.setOutput('dev_email', devEmail);
|
||
core.setOutput('notion_result', notionResult);
|
||
core.setOutput('ticket_id', ticketId);
|
||
core.setOutput('status', status);
|
||
|
||
console.log(`📥 Notion 回传: 广播=${broadcastId}, 状态=${status}, 工单=${ticketId}`);
|
||
|
||
- name: 🔍 铸渊 Agent · 模块二次验证
|
||
id: verify
|
||
env:
|
||
SYSLOG_CONTENT: ${{ steps.callback.outputs.notion_result }}
|
||
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
|
||
AUTHOR: notion-callback
|
||
run: node scripts/verify-modules.js
|
||
|
||
- name: 🧠 二次核验 · 唤醒铸渊核心大脑
|
||
id: persona
|
||
env:
|
||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
|
||
SUBMIT_TYPE: syslog
|
||
SUBMIT_CONTENT: ${{ steps.callback.outputs.notion_result }}
|
||
AUTHOR: notion-callback
|
||
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
|
||
NOTION_CALLBACK_RESULT: ${{ steps.callback.outputs.notion_result }}
|
||
NOTION_TOKEN: ${{ secrets.NOTION_API_KEY }}
|
||
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
|
||
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
|
||
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
|
||
run: node scripts/wake-persona.js
|
||
|
||
- name: 📡 推送广播到 GitHub(Phase B4)
|
||
if: steps.persona.outcome == 'success' && steps.callback.outputs.status == 'approved'
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
|
||
DEVELOPER_NAME: notion-callback
|
||
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
|
||
run: node scripts/push-broadcast-to-github.js
|
||
|
||
- name: 📝 回写 Notion 工单状态
|
||
if: steps.callback.outputs.ticket_id
|
||
env:
|
||
NOTION_TOKEN: ${{ secrets.NOTION_API_KEY }}
|
||
TICKET_ID: ${{ steps.callback.outputs.ticket_id }}
|
||
CALLBACK_STATUS: ${{ steps.callback.outputs.status }}
|
||
run: |
|
||
node -e "
|
||
var https = require('https');
|
||
var token = process.env.NOTION_TOKEN || '';
|
||
var ticketId = process.env.TICKET_ID || '';
|
||
var status = process.env.CALLBACK_STATUS || 'unknown';
|
||
|
||
if (!token || !ticketId) {
|
||
console.log('⚠️ 跳过工单状态更新');
|
||
process.exit(0);
|
||
}
|
||
|
||
var notionStatus = status === 'approved' ? '✅ 已完成' : '⚠️ 异常·等人工介入';
|
||
var body = JSON.stringify({
|
||
properties: {
|
||
'状态': { select: { name: notionStatus } }
|
||
}
|
||
});
|
||
|
||
var opts = {
|
||
hostname: 'api.notion.com',
|
||
port: 443,
|
||
path: '/v1/pages/' + ticketId,
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token,
|
||
'Content-Type': 'application/json',
|
||
'Notion-Version': '2022-06-28',
|
||
'Content-Length': Buffer.byteLength(body)
|
||
}
|
||
};
|
||
|
||
var req = https.request(opts, function(res) {
|
||
var data = '';
|
||
res.on('data', function(c) { data += c; });
|
||
res.on('end', function() {
|
||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||
console.log('✅ 工单状态已更新为 ' + notionStatus);
|
||
} else {
|
||
console.log('⚠️ 工单更新失败: ' + res.statusCode);
|
||
}
|
||
});
|
||
});
|
||
req.on('error', function(e) { console.log('⚠️ ' + e.message); });
|
||
req.write(body);
|
||
req.end();
|
||
"
|
||
|
||
- name: 📧 发送最终确认邮件
|
||
env:
|
||
SMTP_USER: ${{ secrets.SMTP_USER }}
|
||
SMTP_PASS: ${{ secrets.SMTP_PASS }}
|
||
EMAIL_TO: ${{ steps.callback.outputs.dev_email }}
|
||
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
|
||
NOTION_STATUS: ${{ steps.callback.outputs.status }}
|
||
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
|
||
run: |
|
||
node -e "
|
||
const nodemailer = require('nodemailer');
|
||
|
||
const user = process.env.SMTP_USER || '';
|
||
const pass = process.env.SMTP_PASS || '';
|
||
const to = process.env.EMAIL_TO || '';
|
||
const broadcastId = process.env.BROADCAST_ID || '';
|
||
const notionStatus = process.env.NOTION_STATUS || 'unknown';
|
||
const result = process.env.PERSONA_RESULT || '(处理中)';
|
||
|
||
if (!user || !pass) {
|
||
console.log('⚠️ SMTP not configured, skipping email');
|
||
process.exit(0);
|
||
}
|
||
if (!to) {
|
||
console.log('⚠️ No recipient email, skipping');
|
||
process.exit(0);
|
||
}
|
||
|
||
const isApproved = notionStatus === 'approved';
|
||
const subjectText = isApproved
|
||
? '[光湖系统] ' + broadcastId + ' · ✅ 新广播已生成(已核验)'
|
||
: '[光湖系统] ' + broadcastId + ' · ⚠️ 广播需修订';
|
||
|
||
const statusBadge = isApproved
|
||
? '<span style=\"background:#22c55e;color:#fff;padding:4px 12px;border-radius:4px;font-weight:bold\">✅ 已核验通过</span>'
|
||
: '<span style=\"background:#f59e0b;color:#fff;padding:4px 12px;border-radius:4px;font-weight:bold\">⚠️ 需修订</span>';
|
||
|
||
const resultHtml = result
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/\n/g, '<br>');
|
||
|
||
const html = [
|
||
'<div style=\"font-family:sans-serif;max-width:700px;margin:0 auto;padding:24px;background:#0f172a;color:#e2e8f0;border-radius:12px\">',
|
||
'<div style=\"text-align:center;padding:16px 0;border-bottom:1px solid #334155\">',
|
||
'<h2 style=\"color:#60a5fa;margin:0\">🌊 光湖系统 · Notion 回传核验通知</h2>',
|
||
'<p style=\"color:#94a3b8;font-size:14px;margin:8px 0 0\">HoloLake · 铸渊核心大脑二次核验完成</p>',
|
||
'</div>',
|
||
'<div style=\"padding:20px 0\">',
|
||
'<p style=\"color:#22d3ee;font-weight:bold\">📡 ' + broadcastId + '</p>',
|
||
'<p style=\"margin:12px 0\">' + statusBadge + '</p>',
|
||
'<div style=\"background:#1e293b;padding:16px;border-radius:8px;margin:12px 0;line-height:1.8\">' + resultHtml + '</div>',
|
||
'</div>',
|
||
'<div style=\"border-top:1px solid #334155;padding:16px 0;text-align:center\">',
|
||
'<a href=\"https://github.com/qinfendebingshuo/guanghulab\" style=\"display:inline-block;padding:8px 20px;background:linear-gradient(135deg,#3b82f6,#22d3ee);color:#fff;text-decoration:none;border-radius:6px;font-weight:600\">↩ 回到仓库继续开发</a>',
|
||
'<p style=\"color:#64748b;font-size:12px;margin:12px 0 0\">🌀 铸渊 · 代码守护人格体 · Notion↔GitHub 闭环确认</p>',
|
||
'</div>',
|
||
'</div>'
|
||
].join('\n');
|
||
|
||
const transporter = nodemailer.createTransport({
|
||
host: 'smtp.qq.com',
|
||
port: 465,
|
||
secure: true,
|
||
auth: { user, pass }
|
||
});
|
||
|
||
transporter.sendMail({
|
||
from: '\"光湖系统\" <' + user + '>',
|
||
to: to,
|
||
subject: subjectText,
|
||
html: html
|
||
}).then((info) => {
|
||
console.log('✅ 最终确认邮件已发送: ' + info.messageId);
|
||
}).catch((err) => {
|
||
console.log('⚠️ 邮件发送失败: ' + err.message);
|
||
});
|
||
"
|
||
|
||
- name: 📝 更新大脑记忆
|
||
run: |
|
||
node -e "
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const memoryPath = path.join('.github', 'brain', 'memory.json');
|
||
let memory = {};
|
||
try {
|
||
memory = JSON.parse(fs.readFileSync(memoryPath, 'utf8'));
|
||
} catch (_) {
|
||
memory = { events: [] };
|
||
}
|
||
if (!memory.events) memory.events = [];
|
||
|
||
const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
|
||
const status = process.env.NOTION_STATUS || 'unknown';
|
||
const now = new Date().toISOString();
|
||
|
||
// 检查是否已存在相同事件(按 broadcast_id 精确匹配 + 同类型 + 同日期)
|
||
const eventKey = 'Notion 回传核验 · ' + broadcastId;
|
||
const todayStr = now.slice(0, 10);
|
||
const existingIdx = memory.events.findIndex(function(e) {
|
||
return e.type === 'notion_callback' &&
|
||
e.broadcast_id === broadcastId &&
|
||
e.date === todayStr;
|
||
});
|
||
|
||
const newEvent = {
|
||
date: todayStr,
|
||
type: 'notion_callback',
|
||
broadcast_id: broadcastId,
|
||
description: eventKey + ' · 状态: ' + status,
|
||
by: '铸渊Agent·Notion回传管道'
|
||
};
|
||
|
||
if (existingIdx >= 0) {
|
||
// 同一 broadcast_id 只保留最新的一条
|
||
memory.events[existingIdx] = newEvent;
|
||
console.log('ℹ️ 已更新同日同广播事件');
|
||
} else {
|
||
memory.events.unshift(newEvent);
|
||
}
|
||
|
||
// 保留最近 20 条事件
|
||
if (memory.events.length > 20) {
|
||
memory.events = memory.events.slice(0, 20);
|
||
}
|
||
|
||
fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n');
|
||
console.log('✅ 大脑记忆已更新');
|
||
"
|
||
env:
|
||
BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
|
||
NOTION_STATUS: ${{ steps.callback.outputs.status }}
|
||
|
||
- name: 📤 Commit memory update
|
||
run: |
|
||
git config user.name "zhuyuan-agent[bot]"
|
||
git config user.email "zhuyuan-agent[bot]@users.noreply.github.com"
|
||
git add .github/brain/memory.json
|
||
git diff --cached --quiet || git commit -m "🧠 Notion回传核验 · ${{ steps.callback.outputs.broadcast_id }}"
|
||
git push || echo "⚠️ Push skipped (no changes or conflict)"
|