Merge pull request #81 from qinfendebingshuo/copilot/automate-logs-submission-workflow
feat: Phase B — full-loop SYSLOG automation (Notion↔GitHub)
This commit is contained in:
commit
ac17c4715c
|
|
@ -0,0 +1,317 @@
|
|||
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_TOKEN }}
|
||||
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_TOKEN }}
|
||||
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)"
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
name: Notion Heartbeat Monitor
|
||||
# 💓 Phase B3 · 工单心跳监控
|
||||
#
|
||||
# 每 5 分钟检测 Notion 工单队列中的待处理工单:
|
||||
# - 超过 5 分钟未回执 → 重新触发人格体唤醒(最多 3 次)
|
||||
# - 3 次重试仍无回执 → 标记工单异常 + 发邮件通知冰朔
|
||||
#
|
||||
# 依赖 Secrets:
|
||||
# NOTION_API_TOKEN Notion API token
|
||||
# NOTION_TICKET_DB_ID 工单队列数据库 ID
|
||||
# SMTP_USER 邮件发送者
|
||||
# SMTP_PASS 邮件授权码
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/5 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
heartbeat:
|
||||
name: 💓 工单心跳检测
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: 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: 💓 执行心跳检测
|
||||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SMTP_USER: ${{ secrets.SMTP_USER }}
|
||||
SMTP_PASS: ${{ secrets.SMTP_PASS }}
|
||||
ALERT_EMAIL: '565183519@qq.com'
|
||||
run: node scripts/notion-heartbeat.js
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
name: Persona Invoke Endpoint
|
||||
# 🔗 Phase B2 · Notion Agent → 铸渊人格体唤醒
|
||||
#
|
||||
# Notion Agent 通过 workflow_dispatch 触发此工作流,
|
||||
# 等效于 POST /api/persona/invoke 接口。
|
||||
#
|
||||
# 完整流程:
|
||||
# ① Notion Agent 发送 workflow_dispatch(传入工单信息)
|
||||
# ② 铸渊 Agent 从 Notion 读取工单内容
|
||||
# ③ 唤醒人格体(Claude API)处理 SYSLOG
|
||||
# ④ 处理结果写回 Notion 工单(receipt_status = completed)
|
||||
# ⑤ 广播文件推送到 GitHub 仓库 broadcasts/{dev_id}/
|
||||
#
|
||||
# 依赖 Secrets:
|
||||
# LLM_API_KEY 第三方 LLM 平台密钥
|
||||
# LLM_BASE_URL 第三方 LLM 平台 API 地址
|
||||
# NOTION_API_TOKEN Notion API token
|
||||
# NOTION_TICKET_DB_ID 工单队列数据库 ID
|
||||
# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID
|
||||
# PORTRAIT_DB_ID 开发者动态画像库 ID
|
||||
# FINGERPRINT_DB_ID 模块指纹注册表 ID
|
||||
# INVOKE_API_KEY 调用鉴权密钥
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
work_order_id:
|
||||
description: 'Notion 工单页面 ID'
|
||||
required: true
|
||||
type: string
|
||||
task_id:
|
||||
description: '广播编号(如 BC-M23-001-AW)'
|
||||
required: true
|
||||
type: string
|
||||
developer:
|
||||
description: '开发者信息(如 DEV-012 Awen)'
|
||||
required: false
|
||||
default: 'unknown'
|
||||
type: string
|
||||
syslog_raw:
|
||||
description: 'SYSLOG JSON 原文(可选,如为空则从 Notion 读取)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
action:
|
||||
description: '动作类型'
|
||||
required: false
|
||||
default: 'process_syslog'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
invoke:
|
||||
name: 🔗 唤醒人格体处理 SYSLOG
|
||||
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: 🔍 模块验证
|
||||
id: verify
|
||||
env:
|
||||
SYSLOG_CONTENT: ${{ inputs.syslog_raw }}
|
||||
BROADCAST_ID: ${{ inputs.task_id }}
|
||||
AUTHOR: ${{ inputs.developer }}
|
||||
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: ${{ inputs.task_id }}
|
||||
SUBMIT_TYPE: syslog
|
||||
SUBMIT_CONTENT: ${{ inputs.syslog_raw }}
|
||||
AUTHOR: ${{ inputs.developer }}
|
||||
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
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: 📝 回写 Notion 工单
|
||||
if: always()
|
||||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
WORK_ORDER_ID: ${{ inputs.work_order_id }}
|
||||
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
|
||||
TASK_ID: ${{ inputs.task_id }}
|
||||
run: |
|
||||
node -e "
|
||||
var https = require('https');
|
||||
|
||||
var token = process.env.NOTION_TOKEN || '';
|
||||
var workOrderId = process.env.WORK_ORDER_ID || '';
|
||||
var result = process.env.PERSONA_RESULT || '(no result)';
|
||||
var taskId = process.env.TASK_ID || '';
|
||||
|
||||
if (!token || !workOrderId) {
|
||||
console.log('⚠️ 缺少 Notion 配置,跳过回写');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 更新工单状态为 ✅ 已完成
|
||||
var body = JSON.stringify({
|
||||
properties: {
|
||||
'状态': { select: { name: '✅ 已完成' } }
|
||||
}
|
||||
});
|
||||
|
||||
var opts = {
|
||||
hostname: 'api.notion.com',
|
||||
port: 443,
|
||||
path: '/v1/pages/' + workOrderId,
|
||||
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('✅ 工单状态已更新为 ✅ 已完成');
|
||||
} else {
|
||||
console.log('⚠️ 工单状态更新失败: ' + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', function(e) { console.log('⚠️ ' + e.message); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
|
||||
// 追加处理结果到工单
|
||||
var appendBody = JSON.stringify({
|
||||
children: [{
|
||||
object: 'block',
|
||||
type: 'heading_2',
|
||||
heading_2: {
|
||||
rich_text: [{ type: 'text', text: { content: '🧠 人格体处理结果 · receipt_status: completed' } }]
|
||||
}
|
||||
}, {
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
paragraph: {
|
||||
rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
var appendOpts = {
|
||||
hostname: 'api.notion.com',
|
||||
port: 443,
|
||||
path: '/v1/blocks/' + workOrderId + '/children',
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json',
|
||||
'Notion-Version': '2022-06-28',
|
||||
'Content-Length': Buffer.byteLength(appendBody)
|
||||
}
|
||||
};
|
||||
|
||||
var req2 = https.request(appendOpts, function(res) {
|
||||
var data = '';
|
||||
res.on('data', function(c) { data += c; });
|
||||
res.on('end', function() {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
console.log('✅ 处理结果已追加到工单');
|
||||
} else {
|
||||
console.log('⚠️ 结果追加失败: ' + res.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
req2.on('error', function(e) { console.log('⚠️ ' + e.message); });
|
||||
req2.write(appendBody);
|
||||
req2.end();
|
||||
"
|
||||
|
||||
- name: 📡 推送广播到 GitHub
|
||||
if: steps.persona.outcome == 'success'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BROADCAST_ID: ${{ inputs.task_id }}
|
||||
DEVELOPER_ID: ''
|
||||
DEVELOPER_NAME: ${{ inputs.developer }}
|
||||
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
|
||||
run: node scripts/push-broadcast-to-github.js
|
||||
|
||||
- name: 🔴 失败处理
|
||||
if: failure()
|
||||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
WORK_ORDER_ID: ${{ inputs.work_order_id }}
|
||||
run: |
|
||||
node -e "
|
||||
var https = require('https');
|
||||
var token = process.env.NOTION_TOKEN || '';
|
||||
var workOrderId = process.env.WORK_ORDER_ID || '';
|
||||
|
||||
if (!token || !workOrderId) process.exit(0);
|
||||
|
||||
var body = JSON.stringify({
|
||||
properties: {
|
||||
'状态': { select: { name: '⚠️ 异常·等人工介入' } }
|
||||
}
|
||||
});
|
||||
|
||||
var opts = {
|
||||
hostname: 'api.notion.com',
|
||||
port: 443,
|
||||
path: '/v1/pages/' + workOrderId,
|
||||
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() {
|
||||
console.log('工单状态已标记为 ⚠️ 异常');
|
||||
});
|
||||
});
|
||||
req.on('error', function(e) { console.log(e.message); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
"
|
||||
|
|
@ -2,8 +2,18 @@ name: SYSLOG Auto Pipeline
|
|||
# 📡 SYSLOG 自助提交系统 · 全自动闭环
|
||||
#
|
||||
# 开发者在 GitHub Discussion 提交 SYSLOG 或提问
|
||||
# → Actions 自动解析 → 调用 LLM API 唤醒人格体
|
||||
# → 写入 Notion 工单 → 发邮件给开发者 → Discussion 回复
|
||||
# → Actions 自动解析 → 模块上传验证 → 调用 LLM API 唤醒人格体
|
||||
# → 模块闭环测试 → 写入 Notion 工单 → 发邮件给开发者 → Discussion 回复
|
||||
#
|
||||
# 闭环流程:
|
||||
# ① 开发者提交 SYSLOG → Discussion 触发
|
||||
# ② 铸渊 Agent 解析提交内容
|
||||
# ③ 铸渊 Agent 检测模块是否上传到仓库
|
||||
# ④ 唤醒铸渊核心大脑认知(注入模块验证结果)
|
||||
# ⑤ 核心大脑处理 SYSLOG + 生成广播
|
||||
# ⑥ 铸渊 Agent 推送 Notion 工单(触发 Notion 侧处理)
|
||||
# ⑦ 邮件通知开发者
|
||||
# ⑧ Discussion 回复闭环
|
||||
#
|
||||
# 依赖 Secrets:
|
||||
# LLM_API_KEY 第三方 LLM 平台密钥(必须)
|
||||
|
|
@ -96,6 +106,14 @@ jobs:
|
|||
|
||||
console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`);
|
||||
|
||||
- name: 🔍 铸渊 Agent · 模块上传验证
|
||||
id: verify
|
||||
env:
|
||||
SYSLOG_CONTENT: ${{ steps.parse.outputs.content }}
|
||||
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
|
||||
AUTHOR: ${{ steps.parse.outputs.author }}
|
||||
run: node scripts/verify-modules.js
|
||||
|
||||
- name: 🧠 Auto-detect and wake up persona
|
||||
id: persona
|
||||
env:
|
||||
|
|
@ -105,13 +123,15 @@ jobs:
|
|||
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
|
||||
SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
|
||||
AUTHOR: ${{ steps.parse.outputs.author }}
|
||||
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
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: 📋 Write to Notion ticket
|
||||
- name: 📋 创建标准化 Notion 工单(Phase B1)
|
||||
id: ticket
|
||||
if: ${{ secrets.NOTION_API_TOKEN }}
|
||||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
|
|
@ -119,70 +139,20 @@ jobs:
|
|||
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
|
||||
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
|
||||
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
|
||||
run: |
|
||||
node -e "
|
||||
const https = require('https');
|
||||
MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
|
||||
MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
|
||||
DEVELOPER: ${{ steps.parse.outputs.author }}
|
||||
SYSLOG_RAW: ${{ steps.parse.outputs.content }}
|
||||
run: node scripts/create-standardized-ticket.js
|
||||
|
||||
const token = process.env.NOTION_TOKEN;
|
||||
const dbId = process.env.NOTION_TICKET_DB_ID;
|
||||
const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
|
||||
const type = process.env.SUBMIT_TYPE || 'syslog';
|
||||
const result = process.env.PERSONA_RESULT || '(no result)';
|
||||
|
||||
if (!token || !dbId) {
|
||||
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
|
||||
const title = '[自动] ' + broadcastId + ' · ' + typeLabel;
|
||||
|
||||
const body = JSON.stringify({
|
||||
parent: { database_id: dbId },
|
||||
properties: {
|
||||
'标题': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
|
||||
'操作类型': { select: { name: '其他' } },
|
||||
'提交者': { rich_text: [{ type: 'text', text: { content: '自动管道' } }] },
|
||||
'状态': { select: { name: '已完成' } },
|
||||
'优先级': { select: { name: 'P1' } }
|
||||
},
|
||||
children: [{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
paragraph: {
|
||||
rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
const opts = {
|
||||
hostname: 'api.notion.com',
|
||||
port: 443,
|
||||
path: '/v1/pages',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json',
|
||||
'Notion-Version': '2022-06-28',
|
||||
'Content-Length': Buffer.byteLength(body)
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(opts, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (c) => data += c);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
console.log('✅ Notion 工单已创建');
|
||||
} else {
|
||||
console.log('⚠️ Notion 工单创建失败: ' + res.statusCode + ' ' + data);
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => console.log('⚠️ Notion 请求失败: ' + e.message));
|
||||
req.write(body);
|
||||
req.end();
|
||||
"
|
||||
- name: 📡 推送广播到 GitHub(Phase B4)
|
||||
if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
|
||||
DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
|
||||
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
|
||||
run: node scripts/push-broadcast-to-github.js
|
||||
|
||||
- name: 📧 Send email to developer
|
||||
env:
|
||||
|
|
@ -266,11 +236,16 @@ jobs:
|
|||
const type = '${{ steps.parse.outputs.type }}';
|
||||
const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
|
||||
const email = '${{ steps.parse.outputs.email }}';
|
||||
const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
|
||||
const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
|
||||
|
||||
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
|
||||
const maskedEmail = email.length > 4
|
||||
? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
|
||||
: '***';
|
||||
const moduleStatus = moduleCount === '0'
|
||||
? 'ℹ️ 未检测到模块引用'
|
||||
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
|
||||
|
||||
const body = [
|
||||
'✅ **已处理** · ' + typeLabel,
|
||||
|
|
@ -278,13 +253,15 @@ jobs:
|
|||
'| 项目 | 内容 |',
|
||||
'|------|------|',
|
||||
'| 📡 广播编号 | `' + broadcastId + '` |',
|
||||
'| 🔍 模块验证 | ' + moduleStatus + ' |',
|
||||
'| 📧 结果发送至 | `' + maskedEmail + '` |',
|
||||
'| 🤖 处理人格体 | 铸渊 |',
|
||||
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
|
||||
'',
|
||||
'> 结果已发送到你的邮箱,请查收。',
|
||||
'> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
|
||||
'> 结果已发送到你的邮箱,Notion 侧工单已创建。',
|
||||
'>',
|
||||
'> 如未收到,请检查垃圾箱或重新提交。'
|
||||
'> 如未收到邮件,请检查垃圾箱或重新提交。'
|
||||
].join('\n');
|
||||
|
||||
// Use GraphQL to add discussion comment
|
||||
|
|
|
|||
|
|
@ -2,8 +2,18 @@ name: SYSLOG Issue Pipeline
|
|||
# 📡 SYSLOG 自助提交系统 · Issue 版全自动闭环
|
||||
#
|
||||
# 开发者在 GitHub Issue 提交 SYSLOG 或提问(使用 syslog-submit 模板)
|
||||
# → Actions 自动解析 → 调用 LLM API 唤醒人格体
|
||||
# → 写入 Notion 工单 → 发邮件给开发者 → Issue 回复
|
||||
# → Actions 自动解析 → 模块上传验证 → 调用 LLM API 唤醒人格体
|
||||
# → 模块闭环测试 → 写入 Notion 工单 → 发邮件给开发者 → Issue 回复
|
||||
#
|
||||
# 闭环流程:
|
||||
# ① 开发者提交 SYSLOG → Issue 触发
|
||||
# ② 铸渊 Agent 解析提交内容
|
||||
# ③ 铸渊 Agent 检测模块是否上传到仓库
|
||||
# ④ 唤醒铸渊核心大脑认知(注入模块验证结果)
|
||||
# ⑤ 核心大脑处理 SYSLOG + 生成广播
|
||||
# ⑥ 铸渊 Agent 推送 Notion 工单(触发 Notion 侧处理)
|
||||
# ⑦ 邮件通知开发者
|
||||
# ⑧ Issue 回复闭环
|
||||
#
|
||||
# 依赖 Secrets:
|
||||
# LLM_API_KEY 第三方 LLM 平台密钥(必须)
|
||||
|
|
@ -96,6 +106,14 @@ jobs:
|
|||
|
||||
console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`);
|
||||
|
||||
- name: 🔍 铸渊 Agent · 模块上传验证
|
||||
id: verify
|
||||
env:
|
||||
SYSLOG_CONTENT: ${{ steps.parse.outputs.content }}
|
||||
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
|
||||
AUTHOR: ${{ steps.parse.outputs.author }}
|
||||
run: node scripts/verify-modules.js
|
||||
|
||||
- name: 🧠 Auto-detect and wake up persona
|
||||
id: persona
|
||||
env:
|
||||
|
|
@ -105,13 +123,15 @@ jobs:
|
|||
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
|
||||
SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
|
||||
AUTHOR: ${{ steps.parse.outputs.author }}
|
||||
MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
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: 📋 Write to Notion ticket
|
||||
- name: 📋 创建标准化 Notion 工单(Phase B1)
|
||||
id: ticket
|
||||
if: ${{ secrets.NOTION_API_TOKEN }}
|
||||
env:
|
||||
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
|
||||
|
|
@ -119,70 +139,20 @@ jobs:
|
|||
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
|
||||
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
|
||||
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
|
||||
run: |
|
||||
node -e "
|
||||
const https = require('https');
|
||||
MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
|
||||
MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
|
||||
DEVELOPER: ${{ steps.parse.outputs.author }}
|
||||
SYSLOG_RAW: ${{ steps.parse.outputs.content }}
|
||||
run: node scripts/create-standardized-ticket.js
|
||||
|
||||
const token = process.env.NOTION_TOKEN;
|
||||
const dbId = process.env.NOTION_TICKET_DB_ID;
|
||||
const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
|
||||
const type = process.env.SUBMIT_TYPE || 'syslog';
|
||||
const result = process.env.PERSONA_RESULT || '(no result)';
|
||||
|
||||
if (!token || !dbId) {
|
||||
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
|
||||
const title = '[自动] ' + broadcastId + ' · ' + typeLabel;
|
||||
|
||||
const body = JSON.stringify({
|
||||
parent: { database_id: dbId },
|
||||
properties: {
|
||||
'标题': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
|
||||
'操作类型': { select: { name: '其他' } },
|
||||
'提交者': { rich_text: [{ type: 'text', text: { content: '自动管道' } }] },
|
||||
'状态': { select: { name: '已完成' } },
|
||||
'优先级': { select: { name: 'P1' } }
|
||||
},
|
||||
children: [{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
paragraph: {
|
||||
rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
const opts = {
|
||||
hostname: 'api.notion.com',
|
||||
port: 443,
|
||||
path: '/v1/pages',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json',
|
||||
'Notion-Version': '2022-06-28',
|
||||
'Content-Length': Buffer.byteLength(body)
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(opts, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (c) => data += c);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
console.log('✅ Notion 工单已创建');
|
||||
} else {
|
||||
console.log('⚠️ Notion 工单创建失败: ' + res.statusCode + ' ' + data);
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => console.log('⚠️ Notion 请求失败: ' + e.message));
|
||||
req.write(body);
|
||||
req.end();
|
||||
"
|
||||
- name: 📡 推送广播到 GitHub(Phase B4)
|
||||
if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
|
||||
DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
|
||||
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
|
||||
run: node scripts/push-broadcast-to-github.js
|
||||
|
||||
- name: 📧 Send email to developer
|
||||
env:
|
||||
|
|
@ -266,11 +236,16 @@ jobs:
|
|||
const type = '${{ steps.parse.outputs.type }}';
|
||||
const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
|
||||
const email = '${{ steps.parse.outputs.email }}';
|
||||
const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
|
||||
const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
|
||||
|
||||
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
|
||||
const maskedEmail = email.length > 4
|
||||
? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
|
||||
: '***';
|
||||
const moduleStatus = moduleCount === '0'
|
||||
? 'ℹ️ 未检测到模块引用'
|
||||
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
|
||||
|
||||
const body = [
|
||||
'✅ **已处理** · ' + typeLabel,
|
||||
|
|
@ -278,13 +253,15 @@ jobs:
|
|||
'| 项目 | 内容 |',
|
||||
'|------|------|',
|
||||
'| 📡 广播编号 | `' + broadcastId + '` |',
|
||||
'| 🔍 模块验证 | ' + moduleStatus + ' |',
|
||||
'| 📧 结果发送至 | `' + maskedEmail + '` |',
|
||||
'| 🤖 处理人格体 | 铸渊 |',
|
||||
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
|
||||
'',
|
||||
'> 结果已发送到你的邮箱,请查收。',
|
||||
'> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
|
||||
'> 结果已发送到你的邮箱,Notion 侧工单已创建。',
|
||||
'>',
|
||||
'> 如未收到,请检查垃圾箱或重新提交。'
|
||||
'> 如未收到邮件,请检查垃圾箱或重新提交。'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# 📡 broadcasts/
|
||||
|
||||
自动广播归档目录。
|
||||
|
||||
铸渊 Agent 处理完 SYSLOG 后,会自动将新广播文件推送到此目录:
|
||||
|
||||
```
|
||||
broadcasts/
|
||||
DEV-001/
|
||||
BC-M14-001-YY.md
|
||||
DEV-012/
|
||||
BC-M22-009-AW.md
|
||||
BC-M23-001-AW.md
|
||||
...
|
||||
```
|
||||
|
||||
**推送规范**:
|
||||
- 路径:`broadcasts/{developer_id}/{taskId}.md`
|
||||
- 提交信息:`[AutoBroadcast] {taskId} · {开发者名} · {模块名}环节{N}`
|
||||
- 推送方式:通过 GitHub API(`PUT /repos/.../contents/...`)
|
||||
|
||||
**不要手动编辑此目录**,由铸渊 Agent 自动管理。
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
// scripts/create-standardized-ticket.js
|
||||
// 铸渊 · Phase B1 · 标准化工单创建脚本
|
||||
//
|
||||
// 将 SYSLOG 写入 Notion 工单队列时使用标准化格式,
|
||||
// 让 Notion Agent 能够识别和触发后续处理。
|
||||
//
|
||||
// 环境变量:
|
||||
// NOTION_TOKEN Notion API token
|
||||
// NOTION_TICKET_DB_ID 工单队列数据库 ID
|
||||
// BROADCAST_ID 广播编号(如 BC-M23-001-AW)
|
||||
// DEVELOPER 开发者信息(如 "DEV-012 Awen")
|
||||
// SYSLOG_RAW 完整 SYSLOG 原文(JSON 字符串或纯文本)
|
||||
// SUBMIT_TYPE 提交类型(syslog / question)
|
||||
// PERSONA_RESULT 人格体处理结果(可选,首次可为空)
|
||||
// MODULE_VERIFY 模块验证结果 JSON(可选)
|
||||
// MODULES_UPLOADED 模块是否全部上传(true/false)
|
||||
|
||||
'use strict';
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
|
||||
const NOTION_TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID || '';
|
||||
const BROADCAST_ID = process.env.BROADCAST_ID || 'UNKNOWN';
|
||||
const DEVELOPER = process.env.DEVELOPER || process.env.AUTHOR || 'unknown';
|
||||
const SYSLOG_RAW = process.env.SYSLOG_RAW || process.env.SUBMIT_CONTENT || '';
|
||||
const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'syslog';
|
||||
const PERSONA_RESULT = process.env.PERSONA_RESULT || '';
|
||||
const MODULE_VERIFY = process.env.MODULE_VERIFY || '';
|
||||
const MODULES_UPLOADED = process.env.MODULES_UPLOADED || 'false';
|
||||
|
||||
const NOTION_VERSION = '2022-06-28';
|
||||
const NOTION_API_HOSTNAME = 'api.notion.com';
|
||||
const NOTION_RICH_TEXT_MAX = 2000;
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Notion API 工具
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function notionPost(endpoint, body) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var payload = JSON.stringify(body);
|
||||
var opts = {
|
||||
hostname: NOTION_API_HOSTNAME,
|
||||
port: 443,
|
||||
path: endpoint,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + NOTION_TOKEN,
|
||||
'Content-Type': 'application/json',
|
||||
'Notion-Version': NOTION_VERSION,
|
||||
'Content-Length': Buffer.byteLength(payload),
|
||||
},
|
||||
};
|
||||
var req = https.request(opts, function (res) {
|
||||
var data = '';
|
||||
res.on('data', function (chunk) { data += chunk; });
|
||||
res.on('end', function () {
|
||||
try {
|
||||
var parsed = JSON.parse(data);
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(parsed);
|
||||
} else {
|
||||
reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
|
||||
}
|
||||
} catch (e) {
|
||||
reject(new Error('Notion API parse error: ' + data));
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function richTextChunks(content) {
|
||||
var str = String(content || '');
|
||||
var chunks = [];
|
||||
for (var i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) {
|
||||
chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } });
|
||||
}
|
||||
if (chunks.length === 0) {
|
||||
chunks.push({ type: 'text', text: { content: '' } });
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 标准化工单创建
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function createStandardizedTicket() {
|
||||
if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) {
|
||||
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
var now = new Date().toISOString();
|
||||
var typeLabel = SUBMIT_TYPE === 'syslog' ? 'SYSLOG处理' : '提问解答';
|
||||
var ticketStatus = SUBMIT_TYPE === 'syslog' ? '待处理' : '已完成';
|
||||
var verifyStatus = MODULES_UPLOADED === 'true' ? '模块已验证✅' : '模块待验证⚠️';
|
||||
var title = '[自动] ' + BROADCAST_ID + ' · ' + typeLabel + ' · ' + verifyStatus;
|
||||
|
||||
// Phase B1 标准化字段 — 工单内容
|
||||
var ticketContent = [
|
||||
'## 📡 标准化工单 · Phase B1',
|
||||
'',
|
||||
'| 字段 | 值 |',
|
||||
'|------|-----|',
|
||||
'| 工单类型 | ' + typeLabel + ' |',
|
||||
'| 状态 | ' + ticketStatus + ' |',
|
||||
'| 来源 | GitHub Actions |',
|
||||
'| taskId | ' + BROADCAST_ID + ' |',
|
||||
'| developer | ' + DEVELOPER + ' |',
|
||||
'| created_at | ' + now + ' |',
|
||||
'| retry_count | 0 |',
|
||||
'| receipt_status | pending |',
|
||||
'',
|
||||
];
|
||||
|
||||
if (PERSONA_RESULT) {
|
||||
ticketContent.push('## 铸渊核心大脑处理结果');
|
||||
ticketContent.push('');
|
||||
ticketContent.push(PERSONA_RESULT.slice(0, 1500));
|
||||
ticketContent.push('');
|
||||
}
|
||||
|
||||
ticketContent.push('## 模块上传验证');
|
||||
ticketContent.push('');
|
||||
ticketContent.push('模块全部上传: ' + (MODULES_UPLOADED === 'true' ? '✅ 是' : '❌ 否'));
|
||||
ticketContent.push('');
|
||||
|
||||
if (MODULE_VERIFY) {
|
||||
ticketContent.push('验证详情: ' + MODULE_VERIFY.slice(0, 400));
|
||||
ticketContent.push('');
|
||||
}
|
||||
|
||||
var contentText = ticketContent.join('\n');
|
||||
|
||||
// 构建 Notion 页面
|
||||
var body = {
|
||||
parent: { database_id: NOTION_TICKET_DB_ID },
|
||||
properties: {
|
||||
'标题': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
|
||||
'操作类型': { select: { name: '其他' } },
|
||||
'提交者': { rich_text: [{ type: 'text', text: { content: '铸渊Agent·自动管道' } }] },
|
||||
'状态': { select: { name: ticketStatus } },
|
||||
'优先级': { select: { name: 'P1' } },
|
||||
},
|
||||
children: [
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
paragraph: {
|
||||
rich_text: richTextChunks(contentText),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 将 SYSLOG 原文作为代码块附加(如果有)
|
||||
if (SYSLOG_RAW) {
|
||||
body.children.push({
|
||||
object: 'block',
|
||||
type: 'code',
|
||||
code: {
|
||||
rich_text: richTextChunks(SYSLOG_RAW.slice(0, 8000)),
|
||||
language: 'json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('📋 创建标准化工单...');
|
||||
console.log(' taskId: ' + BROADCAST_ID);
|
||||
console.log(' developer: ' + DEVELOPER);
|
||||
console.log(' type: ' + typeLabel);
|
||||
console.log(' receipt_status: pending');
|
||||
|
||||
try {
|
||||
var result = await notionPost('/v1/pages', body);
|
||||
console.log('✅ Notion 标准化工单已创建: ' + result.id);
|
||||
|
||||
// 输出工单 ID 到 GITHUB_OUTPUT
|
||||
var outputFile = process.env.GITHUB_OUTPUT;
|
||||
if (outputFile) {
|
||||
fs.appendFileSync(outputFile, 'ticket_page_id=' + result.id + '\n');
|
||||
fs.appendFileSync(outputFile, 'ticket_url=' + (result.url || '') + '\n');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ 工单创建失败: ' + err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
createStandardizedTicket();
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
// scripts/invoke-persona.js
|
||||
// 铸渊 · Phase B2 · 人格体唤醒调用脚本
|
||||
//
|
||||
// Notion Agent 调用此脚本(通过 workflow_dispatch),
|
||||
// 脚本读取 Notion 工单内容 → 唤醒人格体 → 处理结果写回 Notion。
|
||||
//
|
||||
// 环境变量:
|
||||
// WORK_ORDER_ID Notion 工单页面 ID
|
||||
// TASK_ID 广播编号(如 BC-M23-001-AW)
|
||||
// DEVELOPER 开发者信息(如 "DEV-012 Awen")
|
||||
// SYSLOG_RAW SYSLOG JSON 原文
|
||||
// ACTION 动作类型(process_syslog / retry)
|
||||
// LLM_API_KEY 第三方 LLM 平台密钥
|
||||
// LLM_BASE_URL 第三方 LLM 平台 API 地址
|
||||
// NOTION_TOKEN Notion API token
|
||||
// CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID
|
||||
// PORTRAIT_DB_ID 开发者动态画像库 ID
|
||||
// FINGERPRINT_DB_ID 模块指纹注册表 ID
|
||||
// INVOKE_API_KEY 鉴权密钥(用于 API 调用验证)
|
||||
|
||||
'use strict';
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 配置
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
var WORK_ORDER_ID = process.env.WORK_ORDER_ID || '';
|
||||
var TASK_ID = process.env.TASK_ID || process.env.BROADCAST_ID || 'UNKNOWN';
|
||||
var DEVELOPER = process.env.DEVELOPER || 'unknown';
|
||||
var SYSLOG_RAW = process.env.SYSLOG_RAW || '';
|
||||
var ACTION = process.env.ACTION || 'process_syslog';
|
||||
|
||||
var LLM_API_KEY = process.env.LLM_API_KEY || '';
|
||||
var LLM_BASE_URL = (process.env.LLM_BASE_URL || '').replace(/\/+$/, '');
|
||||
var NOTION_TOKEN = process.env.NOTION_TOKEN || '';
|
||||
var CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || '';
|
||||
var PORTRAIT_DB_ID = process.env.PORTRAIT_DB_ID || '';
|
||||
var FINGERPRINT_DB_ID = process.env.FINGERPRINT_DB_ID || '';
|
||||
|
||||
var NOTION_VERSION = '2022-06-28';
|
||||
var NOTION_API_HOSTNAME = 'api.notion.com';
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// HTTP 工具
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function httpRequest(url, options, body) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var parsed = new URL(url);
|
||||
var isHttps = parsed.protocol === 'https:';
|
||||
var mod = isHttps ? https : http;
|
||||
|
||||
var opts = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || (isHttps ? 443 : 80),
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: options.method || 'GET',
|
||||
headers: options.headers || {},
|
||||
timeout: options.timeout || 120000,
|
||||
};
|
||||
|
||||
var req = mod.request(opts, function (res) {
|
||||
var data = '';
|
||||
res.on('data', function (chunk) { data += chunk; });
|
||||
res.on('end', function () {
|
||||
resolve({ status: res.statusCode, body: data });
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.on('timeout', function () { req.destroy(); reject(new Error('Request timeout')); });
|
||||
|
||||
if (body) {
|
||||
req.write(typeof body === 'string' ? body : JSON.stringify(body));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function notionRequest(method, endpoint, body) {
|
||||
var url = 'https://' + NOTION_API_HOSTNAME + endpoint;
|
||||
var headers = {
|
||||
'Authorization': 'Bearer ' + NOTION_TOKEN,
|
||||
'Content-Type': 'application/json',
|
||||
'Notion-Version': NOTION_VERSION,
|
||||
};
|
||||
return httpRequest(url, { method: method, headers: headers }, body ? JSON.stringify(body) : null)
|
||||
.then(function (res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
if (res.status >= 200 && res.status < 300) return parsed;
|
||||
throw new Error('Notion API ' + res.status + ': ' + (parsed.message || res.body));
|
||||
});
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 1: 读取工单内容(从 Notion)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function readWorkOrder() {
|
||||
if (!WORK_ORDER_ID) {
|
||||
console.log('ℹ️ No WORK_ORDER_ID, using SYSLOG_RAW directly');
|
||||
return { syslog_raw: SYSLOG_RAW, taskId: TASK_ID, developer: DEVELOPER };
|
||||
}
|
||||
|
||||
console.log('📖 读取 Notion 工单: ' + WORK_ORDER_ID);
|
||||
try {
|
||||
var page = await notionRequest('GET', '/v1/pages/' + WORK_ORDER_ID);
|
||||
var props = page.properties || {};
|
||||
|
||||
// 提取工单字段
|
||||
var taskId = TASK_ID;
|
||||
var developer = DEVELOPER;
|
||||
|
||||
// 尝试从工单属性中读取
|
||||
if (props['广播编号'] && props['广播编号'].rich_text) {
|
||||
var bcText = props['广播编号'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
|
||||
if (bcText) taskId = bcText;
|
||||
}
|
||||
if (props['开发者编号'] && props['开发者编号'].rich_text) {
|
||||
var devText = props['开发者编号'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
|
||||
if (devText) developer = devText;
|
||||
}
|
||||
|
||||
// 读取页面内容(子块)来获取 SYSLOG 原文
|
||||
var blocks = await notionRequest('GET', '/v1/blocks/' + WORK_ORDER_ID + '/children?page_size=20');
|
||||
var syslogRaw = SYSLOG_RAW;
|
||||
|
||||
if (blocks.results) {
|
||||
blocks.results.forEach(function (block) {
|
||||
if (block.type === 'code' && block.code && block.code.rich_text) {
|
||||
var codeText = block.code.rich_text.map(function (t) { return t.plain_text || ''; }).join('');
|
||||
if (codeText.length > syslogRaw.length) {
|
||||
syslogRaw = codeText;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(' → taskId: ' + taskId);
|
||||
console.log(' → developer: ' + developer);
|
||||
console.log(' → syslog_raw length: ' + syslogRaw.length);
|
||||
|
||||
return { syslog_raw: syslogRaw, taskId: taskId, developer: developer };
|
||||
} catch (err) {
|
||||
console.log('⚠️ 工单读取失败: ' + err.message + ',使用环境变量');
|
||||
return { syslog_raw: SYSLOG_RAW, taskId: TASK_ID, developer: DEVELOPER };
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 2: 唤醒人格体(复用 wake-persona.js 的逻辑)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function invokePersona(workOrder) {
|
||||
console.log('🧠 唤醒人格体处理 SYSLOG...');
|
||||
|
||||
// 设置环境变量供 wake-persona.js 读取
|
||||
process.env.BROADCAST_ID = workOrder.taskId;
|
||||
process.env.SUBMIT_TYPE = 'syslog';
|
||||
process.env.SUBMIT_CONTENT = workOrder.syslog_raw;
|
||||
process.env.AUTHOR = workOrder.developer;
|
||||
|
||||
// 执行 wake-persona.js(fork 子进程)
|
||||
var wakeScript = path.join(__dirname, 'wake-persona.js');
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var child = childProcess.fork(wakeScript, [], {
|
||||
env: Object.assign({}, process.env, {
|
||||
BROADCAST_ID: workOrder.taskId,
|
||||
SUBMIT_TYPE: 'syslog',
|
||||
SUBMIT_CONTENT: workOrder.syslog_raw,
|
||||
AUTHOR: workOrder.developer,
|
||||
LLM_API_KEY: LLM_API_KEY,
|
||||
LLM_BASE_URL: LLM_BASE_URL,
|
||||
NOTION_TOKEN: NOTION_TOKEN,
|
||||
CORE_BRAIN_PAGE_ID: CORE_BRAIN_PAGE_ID,
|
||||
PORTRAIT_DB_ID: PORTRAIT_DB_ID,
|
||||
FINGERPRINT_DB_ID: FINGERPRINT_DB_ID,
|
||||
}),
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
||||
});
|
||||
|
||||
var stdout = '';
|
||||
var stderr = '';
|
||||
child.stdout.on('data', function (d) { stdout += d; process.stdout.write(d); });
|
||||
child.stderr.on('data', function (d) { stderr += d; process.stderr.write(d); });
|
||||
|
||||
child.on('exit', function (code) {
|
||||
if (code === 0) {
|
||||
// 从 GITHUB_OUTPUT 文件读取 result(如果存在)
|
||||
var outputFile = process.env.GITHUB_OUTPUT;
|
||||
var result = '';
|
||||
if (outputFile && fs.existsSync(outputFile)) {
|
||||
var outputContent = fs.readFileSync(outputFile, 'utf8');
|
||||
var resultMatch = outputContent.match(/result<<EOF_\d+\n([\s\S]*?)\nEOF_\d+/);
|
||||
if (resultMatch) result = resultMatch[1];
|
||||
}
|
||||
if (!result) result = stdout.slice(-3000);
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(new Error('wake-persona.js exited with code ' + code + ': ' + stderr));
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 3: 回写 Notion 工单(更新 receipt_status)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function writeResultsToNotion(workOrderId, result, taskId) {
|
||||
if (!workOrderId || !NOTION_TOKEN) {
|
||||
console.log('⚠️ 无法回写 Notion(缺少 workOrderId 或 NOTION_TOKEN)');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📝 回写 Notion 工单结果...');
|
||||
|
||||
try {
|
||||
// 更新工单状态
|
||||
await notionRequest('PATCH', '/v1/pages/' + workOrderId, {
|
||||
properties: {
|
||||
'状态': { select: { name: '✅ 已完成' } },
|
||||
},
|
||||
});
|
||||
console.log(' → 工单状态更新为 ✅ 已完成');
|
||||
|
||||
// 在工单页面追加处理结果
|
||||
var resultBlocks = [
|
||||
{
|
||||
object: 'block',
|
||||
type: 'heading_2',
|
||||
heading_2: {
|
||||
rich_text: [{ type: 'text', text: { content: '🧠 人格体处理结果' } }],
|
||||
},
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
paragraph: {
|
||||
rich_text: [{ type: 'text', text: { content: '处理时间: ' + new Date().toISOString() } }],
|
||||
},
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
paragraph: {
|
||||
rich_text: [{ type: 'text', text: { content: 'receipt_status: completed' } }],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 将人格体结果作为代码块
|
||||
if (result) {
|
||||
resultBlocks.push({
|
||||
object: 'block',
|
||||
type: 'code',
|
||||
code: {
|
||||
rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }],
|
||||
language: 'markdown',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await notionRequest('PATCH', '/v1/blocks/' + workOrderId + '/children', {
|
||||
children: resultBlocks,
|
||||
});
|
||||
console.log(' → 处理结果已追加到工单页面');
|
||||
} catch (err) {
|
||||
console.error('⚠️ Notion 回写失败: ' + err.message);
|
||||
// 重试一次
|
||||
try {
|
||||
console.log(' → 重试回写...');
|
||||
await notionRequest('PATCH', '/v1/pages/' + workOrderId, {
|
||||
properties: {
|
||||
'状态': { select: { name: '⚠️ 异常·等人工介入' } },
|
||||
},
|
||||
});
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 4: 输出结果
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function outputResult(result) {
|
||||
var outputFile = process.env.GITHUB_OUTPUT;
|
||||
if (outputFile) {
|
||||
var delimiter = 'EOF_' + Date.now();
|
||||
fs.appendFileSync(outputFile, 'persona_result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n');
|
||||
fs.appendFileSync(outputFile, 'invocation_status=completed\n');
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 主流程
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function main() {
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log('🔗 铸渊 · 人格体唤醒调用(Phase B2)');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log(' workOrderId: ' + WORK_ORDER_ID);
|
||||
console.log(' taskId: ' + TASK_ID);
|
||||
console.log(' developer: ' + DEVELOPER);
|
||||
console.log(' action: ' + ACTION);
|
||||
console.log('');
|
||||
|
||||
// Step 1: 读取工单
|
||||
var workOrder = await readWorkOrder();
|
||||
|
||||
if (!workOrder.syslog_raw) {
|
||||
console.error('❌ 缺少 SYSLOG 原文,无法处理');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 2: 唤醒人格体
|
||||
var result;
|
||||
try {
|
||||
result = await invokePersona(workOrder);
|
||||
} catch (err) {
|
||||
console.error('❌ 人格体唤醒失败: ' + err.message);
|
||||
result = '(人格体唤醒失败: ' + err.message + ')';
|
||||
}
|
||||
|
||||
// Step 3: 回写 Notion
|
||||
if (WORK_ORDER_ID) {
|
||||
await writeResultsToNotion(WORK_ORDER_ID, result, workOrder.taskId);
|
||||
}
|
||||
|
||||
// Step 4: 输出结果
|
||||
outputResult(result || '(no result)');
|
||||
|
||||
console.log('');
|
||||
console.log('✅ 人格体调用完成');
|
||||
}
|
||||
|
||||
main().catch(function (err) {
|
||||
console.error('❌ invoke-persona 失败: ' + err.message);
|
||||
|
||||
// 尝试标记工单异常
|
||||
if (WORK_ORDER_ID && NOTION_TOKEN) {
|
||||
notionRequest('PATCH', '/v1/pages/' + WORK_ORDER_ID, {
|
||||
properties: {
|
||||
'状态': { select: { name: '⚠️ 异常·等人工介入' } },
|
||||
},
|
||||
}).catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
// scripts/notion-heartbeat.js
|
||||
// 铸渊 · Phase B3 · 工单心跳监控脚本
|
||||
//
|
||||
// 定时检测 Notion 工单队列中 receipt_status = "pending" 的工单,
|
||||
// 如果超过 5 分钟未回执,重新触发人格体唤醒(最多 3 次)。
|
||||
// 超过 3 次仍无回执,标记工单异常并发邮件通知冰朔。
|
||||
//
|
||||
// 环境变量:
|
||||
// NOTION_TOKEN Notion API token
|
||||
// NOTION_TICKET_DB_ID 工单队列数据库 ID
|
||||
// GITHUB_TOKEN GitHub API token(用于触发 workflow_dispatch)
|
||||
// SMTP_USER 邮件发送者
|
||||
// SMTP_PASS 邮件授权码
|
||||
// ALERT_EMAIL 告警邮箱(默认 565183519@qq.com)
|
||||
// LLM_API_KEY LLM 密钥(传递给 persona-invoke)
|
||||
// LLM_BASE_URL LLM 地址(传递给 persona-invoke)
|
||||
// CORE_BRAIN_PAGE_ID 核心大脑页面 ID
|
||||
// PORTRAIT_DB_ID 画像库 ID
|
||||
// FINGERPRINT_DB_ID 指纹表 ID
|
||||
|
||||
'use strict';
|
||||
|
||||
var https = require('https');
|
||||
|
||||
var NOTION_TOKEN = process.env.NOTION_TOKEN || '';
|
||||
var NOTION_TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID || '';
|
||||
var GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
|
||||
var ALERT_EMAIL = process.env.ALERT_EMAIL || '565183519@qq.com';
|
||||
var SMTP_USER = process.env.SMTP_USER || '';
|
||||
var SMTP_PASS = process.env.SMTP_PASS || '';
|
||||
|
||||
var NOTION_VERSION = '2022-06-28';
|
||||
var HEARTBEAT_TIMEOUT_MS = (parseInt(process.env.HEARTBEAT_TIMEOUT_MINUTES, 10) || 5) * 60 * 1000;
|
||||
var MAX_RETRIES = parseInt(process.env.MAX_RETRY_COUNT, 10) || 3;
|
||||
|
||||
var REPO_OWNER = 'qinfendebingshuo';
|
||||
var REPO_NAME = 'guanghulab';
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// HTTP 工具
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function httpsPost(hostname, apiPath, body, headers) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var payload = JSON.stringify(body);
|
||||
var opts = {
|
||||
hostname: hostname,
|
||||
port: 443,
|
||||
path: apiPath,
|
||||
method: 'POST',
|
||||
headers: Object.assign({
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload),
|
||||
}, headers || {}),
|
||||
};
|
||||
var req = https.request(opts, function (res) {
|
||||
var data = '';
|
||||
res.on('data', function (chunk) { data += chunk; });
|
||||
res.on('end', function () {
|
||||
resolve({ status: res.statusCode, body: data });
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function httpsPatch(hostname, apiPath, body, headers) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var payload = JSON.stringify(body);
|
||||
var opts = {
|
||||
hostname: hostname,
|
||||
port: 443,
|
||||
path: apiPath,
|
||||
method: 'PATCH',
|
||||
headers: Object.assign({
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload),
|
||||
}, headers || {}),
|
||||
};
|
||||
var req = https.request(opts, function (res) {
|
||||
var data = '';
|
||||
res.on('data', function (chunk) { data += chunk; });
|
||||
res.on('end', function () {
|
||||
resolve({ status: res.statusCode, body: data });
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
var notionHeaders = {
|
||||
'Authorization': 'Bearer ' + NOTION_TOKEN,
|
||||
'Notion-Version': NOTION_VERSION,
|
||||
};
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 1: 查询待处理工单
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function queryPendingTickets() {
|
||||
console.log('🔍 查询待处理工单...');
|
||||
|
||||
if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) {
|
||||
console.log('⚠️ 缺少 Notion 配置,跳过');
|
||||
return [];
|
||||
}
|
||||
|
||||
// 查询 receipt_status = pending 的工单
|
||||
// 由于 Notion 数据库可能没有 receipt_status 字段,
|
||||
// 改为查询状态 = "待处理" 的工单
|
||||
var queryBody = {
|
||||
filter: {
|
||||
property: '状态',
|
||||
select: {
|
||||
equals: '待处理',
|
||||
},
|
||||
},
|
||||
page_size: 20,
|
||||
sorts: [{ timestamp: 'created_time', direction: 'ascending' }],
|
||||
};
|
||||
|
||||
try {
|
||||
var res = await httpsPost('api.notion.com', '/v1/databases/' + NOTION_TICKET_DB_ID + '/query', queryBody, notionHeaders);
|
||||
var parsed = JSON.parse(res.body);
|
||||
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
var tickets = parsed.results || [];
|
||||
console.log(' → 找到 ' + tickets.length + ' 个待处理工单');
|
||||
return tickets;
|
||||
} else {
|
||||
console.log('⚠️ 查询失败: ' + (parsed.message || res.body));
|
||||
return [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('⚠️ 查询异常: ' + err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 2: 检测超时工单
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function filterTimedOutTickets(tickets) {
|
||||
var now = Date.now();
|
||||
var timedOut = [];
|
||||
|
||||
tickets.forEach(function (ticket) {
|
||||
var createdTime = new Date(ticket.created_time).getTime();
|
||||
var age = now - createdTime;
|
||||
|
||||
// 只处理来自 "铸渊Agent·自动管道" 的 SYSLOG 工单
|
||||
var props = ticket.properties || {};
|
||||
var submitter = '';
|
||||
if (props['提交者'] && props['提交者'].rich_text) {
|
||||
submitter = props['提交者'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
|
||||
}
|
||||
|
||||
// 只对自动管道创建的工单做心跳检测
|
||||
if (!submitter.includes('铸渊Agent') && !submitter.includes('自动管道')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 超过 5 分钟未完成
|
||||
if (age > HEARTBEAT_TIMEOUT_MS) {
|
||||
var title = '';
|
||||
if (props['标题'] && props['标题'].title) {
|
||||
title = props['标题'].title.map(function (t) { return t.plain_text || ''; }).join('');
|
||||
}
|
||||
|
||||
timedOut.push({
|
||||
id: ticket.id,
|
||||
title: title,
|
||||
createdTime: ticket.created_time,
|
||||
ageMinutes: Math.round(age / 60000),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log(' → ' + timedOut.length + ' 个工单超时(> 5 分钟)');
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 3: 触发重试(通过 workflow_dispatch)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function triggerRetry(ticket) {
|
||||
if (!GITHUB_TOKEN) {
|
||||
console.log('⚠️ 缺少 GITHUB_TOKEN,无法触发重试');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('🔄 重试唤醒: ' + ticket.title + ' (超时 ' + ticket.ageMinutes + ' 分钟)');
|
||||
|
||||
// 从工单标题提取 broadcast_id
|
||||
var bcMatch = (ticket.title || '').match(/BC-[A-Z0-9]+-\d+-[A-Z]+/i);
|
||||
var broadcastId = bcMatch ? bcMatch[0] : 'UNKNOWN';
|
||||
|
||||
try {
|
||||
var res = await httpsPost('api.github.com',
|
||||
'/repos/' + REPO_OWNER + '/' + REPO_NAME + '/actions/workflows/persona-invoke.yml/dispatches',
|
||||
{
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
work_order_id: ticket.id,
|
||||
task_id: broadcastId,
|
||||
developer: 'heartbeat-retry',
|
||||
action: 'retry',
|
||||
},
|
||||
},
|
||||
{
|
||||
'Authorization': 'Bearer ' + GITHUB_TOKEN,
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'User-Agent': 'ZhuyuanHeartbeat/1.0',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}
|
||||
);
|
||||
|
||||
if (res.status === 204 || res.status === 200) {
|
||||
console.log(' → ✅ workflow_dispatch 触发成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' → ⚠️ 触发失败: HTTP ' + res.status + ' ' + res.body);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(' → ⚠️ 触发异常: ' + err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 4: 标记工单异常
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function markTicketError(ticketId) {
|
||||
try {
|
||||
await httpsPatch('api.notion.com', '/v1/pages/' + ticketId, {
|
||||
properties: {
|
||||
'状态': { select: { name: '⚠️ 异常·等人工介入' } },
|
||||
},
|
||||
}, notionHeaders);
|
||||
console.log(' → 工单已标记为 ⚠️ 异常·等人工介入');
|
||||
} catch (err) {
|
||||
console.log(' → 标记失败: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// Step 5: 发送告警邮件
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function sendAlertEmail(timedOutTickets) {
|
||||
if (!SMTP_USER || !SMTP_PASS) {
|
||||
console.log('⚠️ SMTP 未配置,跳过告警邮件');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var nodemailer = require('nodemailer');
|
||||
|
||||
var ticketList = timedOutTickets.map(function (t) {
|
||||
return '- ' + t.title + ' (超时 ' + t.ageMinutes + ' 分钟, 重试已耗尽)';
|
||||
}).join('\n');
|
||||
|
||||
var transporter = nodemailer.createTransport({
|
||||
host: 'smtp.qq.com',
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: { user: SMTP_USER, pass: SMTP_PASS },
|
||||
});
|
||||
|
||||
await transporter.sendMail({
|
||||
from: '"光湖系统·告警" <' + SMTP_USER + '>',
|
||||
to: ALERT_EMAIL,
|
||||
subject: '[光湖系统] ⚠️ SYSLOG 工单处理超时告警',
|
||||
html: [
|
||||
'<div style="font-family:sans-serif;max-width:600px;margin:0 auto;padding:20px;background:#1e1b2e;color:#e2e8f0;border-radius:10px">',
|
||||
'<h2 style="color:#f59e0b">⚠️ 工单处理超时告警</h2>',
|
||||
'<p>以下工单超过 3 次重试仍未收到人格体回执:</p>',
|
||||
'<pre style="background:#0f0d1a;padding:12px;border-radius:6px;color:#94a3b8">' + ticketList + '</pre>',
|
||||
'<p>请登录 Notion 工单队列检查。</p>',
|
||||
'<p style="color:#64748b;font-size:12px;margin-top:16px">🌀 铸渊 · 心跳监控 · 自动告警</p>',
|
||||
'</div>',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
console.log('📧 告警邮件已发送至 ' + ALERT_EMAIL);
|
||||
} catch (err) {
|
||||
console.log('⚠️ 告警邮件发送失败: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 主流程
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function main() {
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log('💓 铸渊 · 工单心跳监控(Phase B3)');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log(' 时间: ' + new Date().toISOString());
|
||||
console.log('');
|
||||
|
||||
// Step 1: 查询待处理工单
|
||||
var tickets = await queryPendingTickets();
|
||||
if (tickets.length === 0) {
|
||||
console.log('✅ 无待处理工单,心跳正常');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: 筛选超时工单
|
||||
var timedOut = filterTimedOutTickets(tickets);
|
||||
if (timedOut.length === 0) {
|
||||
console.log('✅ 所有工单均在处理窗口内,心跳正常');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3 & 4: 对超时工单执行重试或标记异常
|
||||
var errorTickets = [];
|
||||
|
||||
for (var i = 0; i < timedOut.length; i++) {
|
||||
var ticket = timedOut[i];
|
||||
// 基于超时时间估算重试次数(每 5 分钟一次)
|
||||
var estimatedRetries = Math.floor(ticket.ageMinutes / 5);
|
||||
|
||||
if (estimatedRetries < MAX_RETRIES) {
|
||||
// 重试
|
||||
await triggerRetry(ticket);
|
||||
} else {
|
||||
// 超过最大重试次数,标记异常
|
||||
console.log('❌ 工单 ' + ticket.title + ' 重试 ' + MAX_RETRIES + ' 次仍无回执');
|
||||
await markTicketError(ticket.id);
|
||||
errorTickets.push(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: 发送告警邮件(如有异常工单)
|
||||
if (errorTickets.length > 0) {
|
||||
await sendAlertEmail(errorTickets);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('✅ 心跳检测完成');
|
||||
}
|
||||
|
||||
main().catch(function (err) {
|
||||
console.error('❌ 心跳监控失败: ' + err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
// scripts/push-broadcast-to-github.js
|
||||
// 铸渊 · Phase B4 · 广播文件推送到 GitHub 仓库
|
||||
//
|
||||
// 人格体生成广播后,通过 GitHub API 将广播 .md 文件推送到仓库。
|
||||
// 路径规范:broadcasts/{developer_id}/{taskId}.md
|
||||
// 提交信息:[AutoBroadcast] {taskId} · {developer} · {描述}
|
||||
//
|
||||
// 环境变量:
|
||||
// GITHUB_TOKEN GitHub API token(推送用)
|
||||
// BROADCAST_ID 广播编号(如 BC-M23-001-AW)
|
||||
// DEVELOPER_ID 开发者编号(如 DEV-012)
|
||||
// DEVELOPER_NAME 开发者名字(如 Awen)
|
||||
// BROADCAST_CONTENT 广播全文内容(Markdown)
|
||||
// MODULE_NAME 模块名(可选,如 M23)
|
||||
// PHASE_NUMBER 环节号(可选,如 1)
|
||||
|
||||
'use strict';
|
||||
|
||||
var https = require('https');
|
||||
var fs = require('fs');
|
||||
var devSuffixMap = require('./utils/dev-suffix-map');
|
||||
|
||||
var GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
|
||||
var BROADCAST_ID = process.env.BROADCAST_ID || '';
|
||||
var DEVELOPER_ID = process.env.DEVELOPER_ID || '';
|
||||
var DEVELOPER_NAME = process.env.DEVELOPER_NAME || '';
|
||||
var BROADCAST_CONTENT = process.env.BROADCAST_CONTENT || '';
|
||||
var MODULE_NAME = process.env.MODULE_NAME || '';
|
||||
var PHASE_NUMBER = process.env.PHASE_NUMBER || '';
|
||||
|
||||
var REPO_OWNER = 'qinfendebingshuo';
|
||||
var REPO_NAME = 'guanghulab';
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// GitHub API 工具
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function githubRequest(method, apiPath, body) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var payload = body ? JSON.stringify(body) : '';
|
||||
var opts = {
|
||||
hostname: 'api.github.com',
|
||||
port: 443,
|
||||
path: apiPath,
|
||||
method: method,
|
||||
headers: {
|
||||
'User-Agent': 'ZhuyuanBroadcast/1.0',
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
};
|
||||
|
||||
if (GITHUB_TOKEN) {
|
||||
opts.headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
|
||||
}
|
||||
|
||||
if (payload) {
|
||||
opts.headers['Content-Length'] = Buffer.byteLength(payload);
|
||||
}
|
||||
|
||||
var req = https.request(opts, function (res) {
|
||||
var data = '';
|
||||
res.on('data', function (chunk) { data += chunk; });
|
||||
res.on('end', function () {
|
||||
try {
|
||||
resolve({ status: res.statusCode, data: JSON.parse(data) });
|
||||
} catch (e) {
|
||||
resolve({ status: res.statusCode, data: data });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
if (payload) req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 广播推送
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
async function pushBroadcast() {
|
||||
if (!GITHUB_TOKEN) {
|
||||
console.log('⚠️ 缺少 GITHUB_TOKEN,无法推送广播');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!BROADCAST_ID || !BROADCAST_CONTENT) {
|
||||
console.log('⚠️ 缺少 BROADCAST_ID 或 BROADCAST_CONTENT');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 确定开发者 ID(从广播编号提取或使用环境变量)
|
||||
var devId = DEVELOPER_ID;
|
||||
if (!devId) {
|
||||
devId = devSuffixMap.getDevIdFromBroadcast(BROADCAST_ID) || 'UNKNOWN';
|
||||
}
|
||||
|
||||
// 构建路径和提交信息
|
||||
var filePath = 'broadcasts/' + devId + '/' + BROADCAST_ID + '.md';
|
||||
var moduleLabel = MODULE_NAME || BROADCAST_ID.match(/BC-([A-Z0-9-]+)-/i)?.[1] || '';
|
||||
var phaseLabel = PHASE_NUMBER ? '环节' + PHASE_NUMBER : '';
|
||||
var devLabel = DEVELOPER_NAME || devId;
|
||||
var commitMessage = '[AutoBroadcast] ' + BROADCAST_ID + ' · ' + devLabel + ' · ' + moduleLabel + phaseLabel;
|
||||
|
||||
console.log('📡 推送广播到 GitHub...');
|
||||
console.log(' 路径: ' + filePath);
|
||||
console.log(' 提交: ' + commitMessage);
|
||||
|
||||
// 检查文件是否已存在(获取 SHA)
|
||||
var existingRes = await githubRequest('GET',
|
||||
'/repos/' + REPO_OWNER + '/' + REPO_NAME + '/contents/' + filePath);
|
||||
|
||||
var sha = null;
|
||||
if (existingRes.status === 200 && existingRes.data && existingRes.data.sha) {
|
||||
sha = existingRes.data.sha;
|
||||
console.log(' → 文件已存在,将覆盖更新 (sha: ' + sha.slice(0, 8) + ')');
|
||||
}
|
||||
|
||||
// Base64 编码广播内容
|
||||
var contentBase64 = Buffer.from(BROADCAST_CONTENT, 'utf8').toString('base64');
|
||||
|
||||
// 创建或更新文件
|
||||
var putBody = {
|
||||
message: commitMessage,
|
||||
content: contentBase64,
|
||||
committer: {
|
||||
name: 'zhuyuan-agent[bot]',
|
||||
email: 'zhuyuan-agent[bot]@users.noreply.github.com',
|
||||
},
|
||||
};
|
||||
|
||||
if (sha) {
|
||||
putBody.sha = sha;
|
||||
}
|
||||
|
||||
var putRes = await githubRequest('PUT',
|
||||
'/repos/' + REPO_OWNER + '/' + REPO_NAME + '/contents/' + filePath,
|
||||
putBody);
|
||||
|
||||
if (putRes.status === 200 || putRes.status === 201) {
|
||||
console.log('✅ 广播已推送: ' + filePath);
|
||||
console.log(' → commit: ' + (putRes.data.commit ? putRes.data.commit.sha.slice(0, 8) : 'unknown'));
|
||||
|
||||
// 输出到 GITHUB_OUTPUT
|
||||
var outputFile = process.env.GITHUB_OUTPUT;
|
||||
if (outputFile) {
|
||||
fs.appendFileSync(outputFile, 'broadcast_pushed=true\n');
|
||||
fs.appendFileSync(outputFile, 'broadcast_path=' + filePath + '\n');
|
||||
fs.appendFileSync(outputFile, 'broadcast_commit=' + (putRes.data.commit ? putRes.data.commit.sha : '') + '\n');
|
||||
}
|
||||
} else {
|
||||
console.error('❌ 广播推送失败: HTTP ' + putRes.status);
|
||||
console.error(' → ' + JSON.stringify(putRes.data));
|
||||
|
||||
// 输出失败状态
|
||||
var outputFile = process.env.GITHUB_OUTPUT;
|
||||
if (outputFile) {
|
||||
fs.appendFileSync(outputFile, 'broadcast_pushed=false\n');
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pushBroadcast().catch(function (err) {
|
||||
console.error('❌ 广播推送异常: ' + err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -194,11 +194,13 @@ async function createSyslogEntry(syslog, token, dbId, dateStr) {
|
|||
async function createTicket(syslog, token, dbId, dateStr) {
|
||||
const broadcastId = syslog.broadcast_id || syslog.broadcastId || 'UNKNOWN';
|
||||
const devId = syslog.dev_id || syslog.developer_id || 'UNKNOWN';
|
||||
const devName = syslog.developer_name || syslog.dev_name || '';
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const properties = {
|
||||
'标题': titleProp('SYSLOG 回传|' + broadcastId + ' · ' + devId),
|
||||
'标题': titleProp('[自动] SYSLOG处理|' + broadcastId + ' · ' + devId),
|
||||
'操作类型': selectProp('其他'),
|
||||
'提交者': richTextProp('巡检引擎'),
|
||||
'提交者': richTextProp('铸渊Agent·自动管道'),
|
||||
'提交日期': dateProp(dateStr),
|
||||
'状态': selectProp('待处理'),
|
||||
'优先级': selectProp('P1'),
|
||||
|
|
@ -208,6 +210,22 @@ async function createTicket(syslog, token, dbId, dateStr) {
|
|||
if (broadcastId !== 'UNKNOWN') properties['广播编号'] = richTextProp(broadcastId);
|
||||
if (devId !== 'UNKNOWN') properties['开发者编号'] = richTextProp(devId);
|
||||
|
||||
// Phase B1 标准化工单内容
|
||||
const ticketContent = [
|
||||
'## 📡 标准化工单 · Phase B1',
|
||||
'',
|
||||
'| 字段 | 值 |',
|
||||
'|------|-----|',
|
||||
'| 工单类型 | SYSLOG处理 |',
|
||||
'| 状态 | 待处理 |',
|
||||
'| 来源 | GitHub Actions |',
|
||||
'| taskId | ' + broadcastId + ' |',
|
||||
'| developer | ' + devId + ' ' + devName + ' |',
|
||||
'| created_at | ' + now + ' |',
|
||||
'| retry_count | 0 |',
|
||||
'| receipt_status | pending |',
|
||||
].join('\n');
|
||||
|
||||
const body = {
|
||||
parent: { database_id: dbId },
|
||||
properties,
|
||||
|
|
@ -218,7 +236,7 @@ async function createTicket(syslog, token, dbId, dateStr) {
|
|||
paragraph: {
|
||||
rich_text: [{
|
||||
type: 'text',
|
||||
text: { content: '📥 SYSLOG 回传,来自 ' + devId + ',关联广播 ' + broadcastId },
|
||||
text: { content: ticketContent },
|
||||
}],
|
||||
},
|
||||
},
|
||||
|
|
@ -234,7 +252,7 @@ async function createTicket(syslog, token, dbId, dateStr) {
|
|||
};
|
||||
|
||||
const result = await notionPost('/v1/pages', body, token);
|
||||
console.log(' → 霜砚工单已创建: ' + result.id);
|
||||
console.log(' → 标准化工单已创建: ' + result.id);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// scripts/utils/dev-suffix-map.js
|
||||
// 开发者广播编号后缀 → DEV-ID 映射表
|
||||
//
|
||||
// 广播编号格式: BC-{模块}-{序号}-{后缀}
|
||||
// 后缀是开发者名字拼音首字母缩写
|
||||
|
||||
'use strict';
|
||||
|
||||
// 后缀→开发者编号映射
|
||||
var SUFFIX_MAP = {
|
||||
'YY': 'DEV-001', // 页页
|
||||
'FM': 'DEV-002', // 肥猫
|
||||
'YF': 'DEV-003', // 燕樊
|
||||
'ZZ': 'DEV-004', // 之之
|
||||
'XCM': 'DEV-005', // 小草莓
|
||||
'HE': 'DEV-009', // 花尔
|
||||
'JZ': 'DEV-010', // 桔子
|
||||
'CCNN': 'DEV-011', // 匆匆那年
|
||||
'AW': 'DEV-012', // Awen
|
||||
'XX': 'DEV-013', // 小兴
|
||||
'SY': 'DEV-014', // 时雨
|
||||
};
|
||||
|
||||
/**
|
||||
* 从广播编号中提取开发者编号
|
||||
* @param {string} broadcastId - 广播编号(如 BC-M22-009-AW)
|
||||
* @returns {string} 开发者编号(如 DEV-012),未找到返回空字符串
|
||||
*/
|
||||
function getDevIdFromBroadcast(broadcastId) {
|
||||
var suffixMatch = (broadcastId || '').match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
|
||||
var suffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
|
||||
return SUFFIX_MAP[suffix] || '';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SUFFIX_MAP: SUFFIX_MAP,
|
||||
getDevIdFromBroadcast: getDevIdFromBroadcast,
|
||||
};
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
// scripts/verify-modules.js
|
||||
// 铸渊 · 模块上传验证脚本
|
||||
//
|
||||
// 功能:
|
||||
// ① 从 SYSLOG 内容中提取模块编号(M01, M22, M-AUTH 等)
|
||||
// ② 通过 routing-map.json 查找模块对应的目录
|
||||
// ③ 检测目录是否存在于仓库中
|
||||
// ④ 检测 dev-nodes 中开发者节点是否存在
|
||||
// ⑤ 输出验证结果(JSON 格式)到 GITHUB_OUTPUT
|
||||
//
|
||||
// 环境变量:
|
||||
// SYSLOG_CONTENT SYSLOG 全文内容
|
||||
// BROADCAST_ID 广播编号(如 BC-M22-009-AW)
|
||||
// AUTHOR 提交者 GitHub 用户名
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const devSuffixMap = require('./utils/dev-suffix-map');
|
||||
|
||||
const SYSLOG_CONTENT = process.env.SYSLOG_CONTENT || '';
|
||||
const BROADCAST_ID = process.env.BROADCAST_ID || '';
|
||||
const AUTHOR = process.env.AUTHOR || '';
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const ROUTING_MAP_PATH = path.join(REPO_ROOT, 'routing-map.json');
|
||||
const DEV_STATUS_PATH = path.join(REPO_ROOT, '.github', 'persona-brain', 'dev-status.json');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 模块编号提取
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function extractModuleIds(content, broadcastId) {
|
||||
var modules = new Set();
|
||||
|
||||
// 从广播编号提取模块编号(如 BC-M22-009-AW → M22)
|
||||
var bcMatch = broadcastId.match(/BC-([A-Z][A-Z0-9]+(?:-[A-Z]+)?)-/i);
|
||||
if (bcMatch) {
|
||||
modules.add(bcMatch[1].toUpperCase());
|
||||
}
|
||||
|
||||
// 从内容中匹配模块编号模式
|
||||
// 支持: M01, M03, M05, M22, M-AUTH, M-CHANNEL, M-DASHBOARD, M-STATUS, M-MEMORY, M-DINGTALK
|
||||
var patterns = [
|
||||
/\b(M\d{2})\b/gi,
|
||||
/\b(M-[A-Z]+)\b/gi,
|
||||
/模块[::]\s*(M[A-Z0-9-]+)/gi,
|
||||
/module[::]\s*(M[A-Z0-9-]+)/gi,
|
||||
];
|
||||
|
||||
patterns.forEach(function (re) {
|
||||
var match;
|
||||
while ((match = re.exec(content)) !== null) {
|
||||
modules.add(match[1].toUpperCase());
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(modules);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 开发者编号提取
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function extractDevId(content, broadcastId) {
|
||||
// 从内容中直接提取 DEV-XXX(最优先)
|
||||
var devMatch = content.match(/\b(DEV-\d{3})\b/i);
|
||||
if (devMatch) return devMatch[1].toUpperCase();
|
||||
|
||||
// 从广播编号提取开发者编号(使用共享映射表)
|
||||
var mappedDevId = devSuffixMap.getDevIdFromBroadcast(broadcastId);
|
||||
if (mappedDevId) return mappedDevId;
|
||||
|
||||
// 兜底:从 dev-status.json 匹配
|
||||
var suffixMatch = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
|
||||
var devSuffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
|
||||
|
||||
if (devSuffix) {
|
||||
try {
|
||||
var devStatus = JSON.parse(fs.readFileSync(DEV_STATUS_PATH, 'utf8'));
|
||||
var team = devStatus.team || [];
|
||||
for (var i = 0; i < team.length; i++) {
|
||||
var waiting = team[i].waiting || '';
|
||||
if (waiting.includes(broadcastId) || waiting.includes(devSuffix)) {
|
||||
return team[i].dev_id;
|
||||
}
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 模块目录验证
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function verifyModules(moduleIds) {
|
||||
var routingMap = {};
|
||||
try {
|
||||
routingMap = JSON.parse(fs.readFileSync(ROUTING_MAP_PATH, 'utf8'));
|
||||
} catch (_) {
|
||||
console.log('⚠️ routing-map.json 不存在或格式错误');
|
||||
}
|
||||
|
||||
var modules = routingMap.modules || {};
|
||||
var results = [];
|
||||
|
||||
moduleIds.forEach(function (modId) {
|
||||
var entry = modules[modId];
|
||||
var result = {
|
||||
module_id: modId,
|
||||
registered: false,
|
||||
dir_exists: false,
|
||||
dir_path: '',
|
||||
dev_id: '',
|
||||
status: '',
|
||||
files_found: 0,
|
||||
};
|
||||
|
||||
if (entry) {
|
||||
result.registered = true;
|
||||
result.dir_path = entry.dir || '';
|
||||
result.dev_id = entry.dev || '';
|
||||
result.status = entry.status || '';
|
||||
|
||||
// 检查目录是否存在
|
||||
var dirFullPath = path.join(REPO_ROOT, entry.dir);
|
||||
if (fs.existsSync(dirFullPath)) {
|
||||
result.dir_exists = true;
|
||||
// 统计文件数量(不含隐藏文件)
|
||||
try {
|
||||
var files = fs.readdirSync(dirFullPath).filter(function (f) {
|
||||
return !f.startsWith('.');
|
||||
});
|
||||
result.files_found = files.length;
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
} else {
|
||||
// 模块未在 routing-map 中注册,尝试直接查找
|
||||
var candidates = [
|
||||
modId.toLowerCase(),
|
||||
'm' + modId.replace(/^M/i, '').toLowerCase(),
|
||||
'dev-nodes/' + modId,
|
||||
];
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
var candidatePath = path.join(REPO_ROOT, candidates[i]);
|
||||
if (fs.existsSync(candidatePath)) {
|
||||
result.dir_exists = true;
|
||||
result.dir_path = candidates[i];
|
||||
try {
|
||||
var files = fs.readdirSync(candidatePath).filter(function (f) {
|
||||
return !f.startsWith('.');
|
||||
});
|
||||
result.files_found = files.length;
|
||||
} catch (_) { /* ignore */ }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 开发者节点验证
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function verifyDevNode(devId) {
|
||||
if (!devId) return { exists: false, path: '', files: 0 };
|
||||
|
||||
var devNodePath = path.join(REPO_ROOT, 'dev-nodes', devId);
|
||||
var result = { exists: false, path: 'dev-nodes/' + devId, files: 0 };
|
||||
|
||||
if (fs.existsSync(devNodePath)) {
|
||||
result.exists = true;
|
||||
try {
|
||||
var files = fs.readdirSync(devNodePath).filter(function (f) {
|
||||
return !f.startsWith('.');
|
||||
});
|
||||
result.files = files.length;
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// 主流程
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
function main() {
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log('🔍 铸渊 · 模块上传验证');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log(' 广播编号: ' + BROADCAST_ID);
|
||||
console.log(' 提交者: ' + AUTHOR);
|
||||
console.log(' 内容长度: ' + SYSLOG_CONTENT.length + ' 字符');
|
||||
console.log('');
|
||||
|
||||
// ① 提取模块编号
|
||||
var moduleIds = extractModuleIds(SYSLOG_CONTENT, BROADCAST_ID);
|
||||
console.log('📦 识别到模块: ' + (moduleIds.length > 0 ? moduleIds.join(', ') : '(无)'));
|
||||
|
||||
// ② 提取开发者编号
|
||||
var devId = extractDevId(SYSLOG_CONTENT, BROADCAST_ID);
|
||||
console.log('👤 开发者编号: ' + (devId || '(未识别)'));
|
||||
|
||||
// ③ 验证模块目录
|
||||
var moduleResults = verifyModules(moduleIds);
|
||||
|
||||
// ④ 验证开发者节点
|
||||
var devNodeResult = verifyDevNode(devId);
|
||||
|
||||
// ⑤ 汇总结果
|
||||
var allModulesUploaded = moduleResults.length > 0 && moduleResults.every(function (r) {
|
||||
return r.dir_exists;
|
||||
});
|
||||
|
||||
var summary = {
|
||||
broadcast_id: BROADCAST_ID,
|
||||
author: AUTHOR,
|
||||
dev_id: devId,
|
||||
modules_detected: moduleIds,
|
||||
module_count: moduleIds.length,
|
||||
module_results: moduleResults,
|
||||
dev_node: devNodeResult,
|
||||
all_modules_uploaded: allModulesUploaded,
|
||||
verification_passed: allModulesUploaded || moduleIds.length === 0,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// ⑥ 输出报告
|
||||
console.log('');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
console.log('📋 验证报告');
|
||||
console.log('═══════════════════════════════════════════');
|
||||
|
||||
if (moduleResults.length === 0) {
|
||||
console.log(' ℹ️ 未检测到模块引用,跳过模块验证');
|
||||
} else {
|
||||
moduleResults.forEach(function (r) {
|
||||
var icon = r.dir_exists ? '✅' : '❌';
|
||||
console.log(' ' + icon + ' ' + r.module_id +
|
||||
' → ' + (r.dir_path || '(未注册)') +
|
||||
(r.dir_exists ? ' (' + r.files_found + ' 个文件)' : ' (目录不存在)'));
|
||||
});
|
||||
}
|
||||
|
||||
console.log(' ' + (devNodeResult.exists ? '✅' : '⚠️') +
|
||||
' 开发者节点: ' + devNodeResult.path +
|
||||
(devNodeResult.exists ? ' (' + devNodeResult.files + ' 个文件)' : ' (不存在)'));
|
||||
|
||||
console.log('');
|
||||
console.log(' 总结: ' + (summary.verification_passed ? '✅ 验证通过' : '❌ 模块未完整上传'));
|
||||
|
||||
// ⑦ 写入 GITHUB_OUTPUT
|
||||
var outputFile = process.env.GITHUB_OUTPUT;
|
||||
if (outputFile) {
|
||||
var delimiter = 'EOF_' + Date.now();
|
||||
var jsonStr = JSON.stringify(summary);
|
||||
fs.appendFileSync(outputFile, 'verify_result<<' + delimiter + '\n' + jsonStr + '\n' + delimiter + '\n');
|
||||
fs.appendFileSync(outputFile, 'modules_uploaded=' + (allModulesUploaded ? 'true' : 'false') + '\n');
|
||||
fs.appendFileSync(outputFile, 'verification_passed=' + (summary.verification_passed ? 'true' : 'false') + '\n');
|
||||
fs.appendFileSync(outputFile, 'module_count=' + moduleIds.length + '\n');
|
||||
}
|
||||
|
||||
// ⑧ 将汇总文本输出(供后续步骤作为上下文)
|
||||
var reportLines = [
|
||||
'## 🔍 模块上传验证报告',
|
||||
'',
|
||||
'| 项目 | 结果 |',
|
||||
'|------|------|',
|
||||
'| 广播编号 | ' + BROADCAST_ID + ' |',
|
||||
'| 开发者 | ' + (devId || AUTHOR) + ' |',
|
||||
'| 检测模块数 | ' + moduleIds.length + ' |',
|
||||
'| 全部上传 | ' + (allModulesUploaded ? '✅ 是' : '❌ 否') + ' |',
|
||||
'| 验证结果 | ' + (summary.verification_passed ? '✅ 通过' : '❌ 未通过') + ' |',
|
||||
];
|
||||
|
||||
if (moduleResults.length > 0) {
|
||||
reportLines.push('');
|
||||
reportLines.push('### 模块详情');
|
||||
reportLines.push('| 模块 | 目录 | 状态 | 文件数 |');
|
||||
reportLines.push('|------|------|------|--------|');
|
||||
moduleResults.forEach(function (r) {
|
||||
reportLines.push('| ' + r.module_id + ' | ' + (r.dir_path || '-') +
|
||||
' | ' + (r.dir_exists ? '✅ 已上传' : '❌ 未找到') +
|
||||
' | ' + r.files_found + ' |');
|
||||
});
|
||||
}
|
||||
|
||||
if (outputFile) {
|
||||
var reportDelimiter = 'REPORT_EOF_' + Date.now();
|
||||
fs.appendFileSync(outputFile, 'verify_report<<' + reportDelimiter + '\n' + reportLines.join('\n') + '\n' + reportDelimiter + '\n');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('✅ 验证完成');
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -38,6 +38,11 @@ const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'question';
|
|||
const SUBMIT_CONTENT = process.env.SUBMIT_CONTENT || '';
|
||||
const AUTHOR = process.env.AUTHOR || 'unknown';
|
||||
|
||||
// 模块验证结果注入(由 verify-modules.js 提供)
|
||||
const MODULE_VERIFY_RESULT = process.env.MODULE_VERIFY_RESULT || '';
|
||||
// Notion 回传结果注入(由 notion-callback-pipeline 提供)
|
||||
const NOTION_CALLBACK_RESULT = process.env.NOTION_CALLBACK_RESULT || '';
|
||||
|
||||
// Notion 配置(v4.0 协议动态注入)
|
||||
const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
|
||||
const CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || '';
|
||||
|
|
@ -764,6 +769,34 @@ async function buildSystemPrompt(type, broadcastId, author) {
|
|||
parts.push('此规则优先级最高,覆盖核心大脑中「广播不写代码」的默认规则。');
|
||||
parts.push('此规则仅适用于自动化链路(Claude API 出广播),手动链路不受影响。');
|
||||
|
||||
// ━━━ 模块验证结果注入(铸渊 Agent 检测结果) ━━━
|
||||
if (MODULE_VERIFY_RESULT) {
|
||||
parts.push('');
|
||||
parts.push('═══════════════════════════════════════════');
|
||||
parts.push('## 🔍 铸渊 Agent · 模块上传验证结果');
|
||||
parts.push('═══════════════════════════════════════════');
|
||||
parts.push('');
|
||||
parts.push('以下是铸渊 Agent 在仓库内自动检测的模块上传验证结果:');
|
||||
parts.push(MODULE_VERIFY_RESULT);
|
||||
parts.push('');
|
||||
parts.push('请根据验证结果决定是否接受 SYSLOG:');
|
||||
parts.push('- 如果模块已上传(✅),继续正常闭环流程');
|
||||
parts.push('- 如果模块未上传(❌),在验收报告中标注"需补充",并在反馈中指出缺失的模块');
|
||||
}
|
||||
|
||||
// ━━━ Notion 回传结果注入(二次核验) ━━━
|
||||
if (NOTION_CALLBACK_RESULT) {
|
||||
parts.push('');
|
||||
parts.push('═══════════════════════════════════════════');
|
||||
parts.push('## 📥 Notion 侧处理结果(回传核验)');
|
||||
parts.push('═══════════════════════════════════════════');
|
||||
parts.push('');
|
||||
parts.push('以下是 Notion 侧核心大脑人格体处理后的回传结果:');
|
||||
parts.push(NOTION_CALLBACK_RESULT);
|
||||
parts.push('');
|
||||
parts.push('请核对以上回传内容,确认新广播生成无误后输出最终确认。');
|
||||
}
|
||||
|
||||
// ━━━ 任务类型专用指令 ━━━
|
||||
if (type === 'syslog') {
|
||||
parts.push('');
|
||||
|
|
@ -773,17 +806,19 @@ async function buildSystemPrompt(type, broadcastId, author) {
|
|||
parts.push('');
|
||||
parts.push('你需要完成以下工作:');
|
||||
parts.push('1. 验收 SYSLOG(检查 MODULE_LOG 完整性)');
|
||||
parts.push('2. 查询画像库最近 2-3 条快照(PGP v1.0)');
|
||||
parts.push('3. 查询模块指纹注册表(防重复·⑨.5)');
|
||||
parts.push('4. RT-02 自动调度判断');
|
||||
parts.push('5. 生成新广播(BC-GEN v4.0 完整流程)');
|
||||
parts.push('6. 输出结构化结果(广播全文 + 闭环数据)');
|
||||
parts.push('2. 检查模块上传验证结果(铸渊 Agent 已自动检测)');
|
||||
parts.push('3. 查询画像库最近 2-3 条快照(PGP v1.0)');
|
||||
parts.push('4. 查询模块指纹注册表(防重复·⑨.5)');
|
||||
parts.push('5. RT-02 自动调度判断');
|
||||
parts.push('6. 生成新广播(BC-GEN v4.0 完整流程)');
|
||||
parts.push('7. 输出结构化结果(广播全文 + 闭环数据)');
|
||||
parts.push('');
|
||||
parts.push('输出格式:');
|
||||
parts.push('---');
|
||||
parts.push('## 📡 SYSLOG 验收报告');
|
||||
parts.push('### 广播编号:[编号]');
|
||||
parts.push('### 验收结果:[通过/需补充]');
|
||||
parts.push('### 模块验证:[已上传/未上传,列出详情]');
|
||||
parts.push('### 工作总结:[摘要]');
|
||||
parts.push('### 画像评估:[PGP 五维度评分]');
|
||||
parts.push('### 调度判断:[RT-02 下一步]');
|
||||
|
|
|
|||
Loading…
Reference in New Issue