From 0e977af2a0e18e6b07721f5b08fb0301109bfa55 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 05:56:45 +0000
Subject: [PATCH 1/6] Initial plan
From bce083dc665f4c72a272a7aa7711c2ff4fd07e4a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:05:53 +0000
Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E9=93=B8=E6=B8=8A=20SYSLOG=20?=
=?UTF-8?q?=E5=85=A8=E9=97=AD=E7=8E=AF=E5=B7=A5=E4=BD=9C=E6=B5=81=20=C2=B7?=
=?UTF-8?q?=20=E6=A8=A1=E5=9D=97=E9=AA=8C=E8=AF=81=20+=20=E6=A0=B8?=
=?UTF-8?q?=E5=BF=83=E5=A4=A7=E8=84=91=E5=94=A4=E9=86=92=20+=20Notion=20?=
=?UTF-8?q?=E5=9B=9E=E4=BC=A0=E7=AE=A1=E9=81=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增:
- scripts/verify-modules.js: 模块上传验证脚本
- .github/workflows/notion-callback-pipeline.yml: Notion→GitHub 回传核验管道
增强:
- syslog-issue-pipeline.yml: 新增模块验证步骤 + 核心大脑注入验证结果
- syslog-auto-pipeline.yml: 同步增强
- wake-persona.js: 支持 MODULE_VERIFY_RESULT + NOTION_CALLBACK_RESULT 注入
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.../workflows/notion-callback-pipeline.yml | 247 ++++++++++++++
.github/workflows/syslog-auto-pipeline.yml | 63 +++-
.github/workflows/syslog-issue-pipeline.yml | 63 +++-
scripts/verify-modules.js | 303 ++++++++++++++++++
scripts/wake-persona.js | 45 ++-
5 files changed, 700 insertions(+), 21 deletions(-)
create mode 100644 .github/workflows/notion-callback-pipeline.yml
create mode 100644 scripts/verify-modules.js
diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml
new file mode 100644
index 00000000..6be491f6
--- /dev/null
+++ b/.github/workflows/notion-callback-pipeline.yml
@@ -0,0 +1,247 @@
+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: 📧 发送最终确认邮件
+ 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
+ ? '✅ 已核验通过'
+ : '⚠️ 需修订';
+
+ const resultHtml = result
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\n/g, '
');
+
+ const html = [
+ '
',
+ '
',
+ '
🌊 光湖系统 · Notion 回传核验通知
',
+ '
HoloLake · 铸渊核心大脑二次核验完成
',
+ '
',
+ '
',
+ '
📡 ' + broadcastId + '
',
+ '
' + statusBadge + '
',
+ '
' + resultHtml + '
',
+ '
',
+ '
',
+ '
↩ 回到仓库继续开发',
+ '
🌀 铸渊 · 代码守护人格体 · Notion↔GitHub 闭环确认
',
+ '
',
+ '
'
+ ].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();
+
+ // 检查是否已存在相同事件
+ const exists = memory.events.some(function(e) {
+ return e.type === 'notion_callback' &&
+ e.description && e.description.includes(broadcastId) &&
+ e.date === now.slice(0, 10);
+ });
+
+ if (!exists) {
+ memory.events.unshift({
+ date: now.slice(0, 10),
+ type: 'notion_callback',
+ description: 'Notion 回传核验 · ' + broadcastId + ' · 状态: ' + status,
+ by: '铸渊Agent·Notion回传管道'
+ });
+
+ // 保留最近 20 条事件
+ if (memory.events.length > 20) {
+ memory.events = memory.events.slice(0, 20);
+ }
+
+ fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n');
+ console.log('✅ 大脑记忆已更新');
+ } else {
+ 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)"
diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml
index c2fa3898..1344c3e2 100644
--- a/.github/workflows/syslog-auto-pipeline.yml
+++ b/.github/workflows/syslog-auto-pipeline.yml
@@ -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,6 +123,7 @@ 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 }}
@@ -119,6 +138,9 @@ jobs:
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
+ MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
run: |
node -e "
const https = require('https');
@@ -128,6 +150,9 @@ jobs:
const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
const type = process.env.SUBMIT_TYPE || 'syslog';
const result = process.env.PERSONA_RESULT || '(no result)';
+ const modulesUploaded = process.env.MODULES_UPLOADED || 'false';
+ const moduleVerify = process.env.MODULE_VERIFY || '{}';
+ const author = process.env.AUTHOR || 'unknown';
if (!token || !dbId) {
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
@@ -135,22 +160,37 @@ jobs:
}
const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
- const title = '[自动] ' + broadcastId + ' · ' + typeLabel;
+ const verifyStatus = modulesUploaded === 'true' ? '模块已验证✅' : '模块待验证⚠️';
+ const title = '[自动] ' + broadcastId + ' · ' + typeLabel + ' · ' + verifyStatus;
+
+ // 构建工单内容:包含人格体处理结果 + 模块验证数据
+ const ticketContent = [
+ '## 铸渊核心大脑处理结果',
+ '',
+ result.slice(0, 1500),
+ '',
+ '## 模块上传验证',
+ '',
+ '模块全部上传: ' + (modulesUploaded === 'true' ? '✅ 是' : '❌ 否'),
+ '提交者: ' + author,
+ '',
+ '验证详情: ' + moduleVerify.slice(0, 400),
+ ].join('\\n');
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: '已完成' } },
+ '提交者': { rich_text: [{ type: 'text', text: { content: '铸渊Agent·自动管道' } }] },
+ '状态': { select: { name: '待处理' } },
'优先级': { select: { name: 'P1' } }
},
children: [{
object: 'block',
type: 'paragraph',
paragraph: {
- rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
+ rich_text: [{ type: 'text', text: { content: ticketContent.slice(0, 2000) } }]
}
}]
});
@@ -266,11 +306,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 +323,15 @@ jobs:
'| 项目 | 内容 |',
'|------|------|',
'| 📡 广播编号 | `' + broadcastId + '` |',
+ '| 🔍 模块验证 | ' + moduleStatus + ' |',
'| 📧 结果发送至 | `' + maskedEmail + '` |',
'| 🤖 处理人格体 | 铸渊 |',
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
'',
- '> 结果已发送到你的邮箱,请查收。',
+ '> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
+ '> 结果已发送到你的邮箱,Notion 侧工单已创建。',
'>',
- '> 如未收到,请检查垃圾箱或重新提交。'
+ '> 如未收到邮件,请检查垃圾箱或重新提交。'
].join('\n');
// Use GraphQL to add discussion comment
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index 5a5d81a5..de09bac0 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -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,6 +123,7 @@ 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 }}
@@ -119,6 +138,9 @@ jobs:
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
+ MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
run: |
node -e "
const https = require('https');
@@ -128,6 +150,9 @@ jobs:
const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
const type = process.env.SUBMIT_TYPE || 'syslog';
const result = process.env.PERSONA_RESULT || '(no result)';
+ const modulesUploaded = process.env.MODULES_UPLOADED || 'false';
+ const moduleVerify = process.env.MODULE_VERIFY || '{}';
+ const author = process.env.AUTHOR || 'unknown';
if (!token || !dbId) {
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
@@ -135,22 +160,37 @@ jobs:
}
const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
- const title = '[自动] ' + broadcastId + ' · ' + typeLabel;
+ const verifyStatus = modulesUploaded === 'true' ? '模块已验证✅' : '模块待验证⚠️';
+ const title = '[自动] ' + broadcastId + ' · ' + typeLabel + ' · ' + verifyStatus;
+
+ // 构建工单内容:包含人格体处理结果 + 模块验证数据
+ const ticketContent = [
+ '## 铸渊核心大脑处理结果',
+ '',
+ result.slice(0, 1500),
+ '',
+ '## 模块上传验证',
+ '',
+ '模块全部上传: ' + (modulesUploaded === 'true' ? '✅ 是' : '❌ 否'),
+ '提交者: ' + author,
+ '',
+ '验证详情: ' + moduleVerify.slice(0, 400),
+ ].join('\\n');
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: '已完成' } },
+ '提交者': { rich_text: [{ type: 'text', text: { content: '铸渊Agent·自动管道' } }] },
+ '状态': { select: { name: '待处理' } },
'优先级': { select: { name: 'P1' } }
},
children: [{
object: 'block',
type: 'paragraph',
paragraph: {
- rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
+ rich_text: [{ type: 'text', text: { content: ticketContent.slice(0, 2000) } }]
}
}]
});
@@ -266,11 +306,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 +323,15 @@ jobs:
'| 项目 | 内容 |',
'|------|------|',
'| 📡 广播编号 | `' + broadcastId + '` |',
+ '| 🔍 模块验证 | ' + moduleStatus + ' |',
'| 📧 结果发送至 | `' + maskedEmail + '` |',
'| 🤖 处理人格体 | 铸渊 |',
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
'',
- '> 结果已发送到你的邮箱,请查收。',
+ '> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
+ '> 结果已发送到你的邮箱,Notion 侧工单已创建。',
'>',
- '> 如未收到,请检查垃圾箱或重新提交。'
+ '> 如未收到邮件,请检查垃圾箱或重新提交。'
].join('\n');
await github.rest.issues.createComment({
diff --git a/scripts/verify-modules.js b/scripts/verify-modules.js
new file mode 100644
index 00000000..807ec605
--- /dev/null
+++ b/scripts/verify-modules.js
@@ -0,0 +1,303 @@
+// 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 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) {
+ // 从广播编号提取开发者后缀(如 BC-M22-009-AW → AW)
+ var suffixMatch = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ var devSuffix = suffixMatch ? suffixMatch[1] : '';
+
+ // 从内容中提取 DEV-XXX
+ var devMatch = content.match(/\b(DEV-\d{3})\b/i);
+ if (devMatch) return devMatch[1].toUpperCase();
+
+ // 从 dev-status.json 通过后缀查找
+ 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 name = team[i].name || '';
+ // 简单匹配:用名字首字母缩写匹配后缀
+ if (name && devSuffix.length >= 2) {
+ 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();
diff --git a/scripts/wake-persona.js b/scripts/wake-persona.js
index fb88b08e..1ca304d5 100644
--- a/scripts/wake-persona.js
+++ b/scripts/wake-persona.js
@@ -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 下一步]');
From a6567ff2d20e251223e5540269230562c4070033 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:08:13 +0000
Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=AE=A1=E6=9F=A5=E9=97=AE=E9=A2=98=20-=20extractDevId=20?=
=?UTF-8?q?=E9=80=BB=E8=BE=91=20+=20=E4=BA=8B=E4=BB=B6=E5=8E=BB=E9=87=8D?=
=?UTF-8?q?=20+=20=E5=B7=A5=E5=8D=95=E7=8A=B6=E6=80=81=E6=9D=A1=E4=BB=B6?=
=?UTF-8?q?=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.../workflows/notion-callback-pipeline.yml | 45 +++++++++++--------
.github/workflows/syslog-auto-pipeline.yml | 6 ++-
.github/workflows/syslog-issue-pipeline.yml | 6 ++-
scripts/verify-modules.js | 27 +++++++----
4 files changed, 54 insertions(+), 30 deletions(-)
diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml
index 6be491f6..a6fc2d39 100644
--- a/.github/workflows/notion-callback-pipeline.yml
+++ b/.github/workflows/notion-callback-pipeline.yml
@@ -208,31 +208,38 @@ jobs:
const status = process.env.NOTION_STATUS || 'unknown';
const now = new Date().toISOString();
- // 检查是否已存在相同事件
- const exists = memory.events.some(function(e) {
+ // 检查是否已存在相同事件(按 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.description && e.description.includes(broadcastId) &&
- e.date === now.slice(0, 10);
+ e.broadcast_id === broadcastId &&
+ e.date === todayStr;
});
- if (!exists) {
- memory.events.unshift({
- date: now.slice(0, 10),
- type: 'notion_callback',
- description: 'Notion 回传核验 · ' + broadcastId + ' · 状态: ' + status,
- by: '铸渊Agent·Notion回传管道'
- });
+ const newEvent = {
+ date: todayStr,
+ type: 'notion_callback',
+ broadcast_id: broadcastId,
+ description: eventKey + ' · 状态: ' + status,
+ by: '铸渊Agent·Notion回传管道'
+ };
- // 保留最近 20 条事件
- if (memory.events.length > 20) {
- memory.events = memory.events.slice(0, 20);
- }
-
- fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n');
- console.log('✅ 大脑记忆已更新');
+ if (existingIdx >= 0) {
+ // 同一 broadcast_id 只保留最新的一条
+ memory.events[existingIdx] = newEvent;
+ console.log('ℹ️ 已更新同日同广播事件');
} else {
- console.log('ℹ️ 事件已存在,跳过');
+ 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 }}
diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml
index 1344c3e2..02eb3e5e 100644
--- a/.github/workflows/syslog-auto-pipeline.yml
+++ b/.github/workflows/syslog-auto-pipeline.yml
@@ -177,13 +177,17 @@ jobs:
'验证详情: ' + moduleVerify.slice(0, 400),
].join('\\n');
+ // SYSLOG类型工单设为'待处理'(等待Notion侧核心大脑处理)
+ // 提问类型工单设为'已完成'(铸渊已生成解答)
+ const ticketStatus = type === 'syslog' ? '待处理' : '已完成';
+
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: '铸渊Agent·自动管道' } }] },
- '状态': { select: { name: '待处理' } },
+ '状态': { select: { name: ticketStatus } },
'优先级': { select: { name: 'P1' } }
},
children: [{
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index de09bac0..4acbc3b0 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -177,13 +177,17 @@ jobs:
'验证详情: ' + moduleVerify.slice(0, 400),
].join('\\n');
+ // SYSLOG类型工单设为'待处理'(等待Notion侧核心大脑处理)
+ // 提问类型工单设为'已完成'(铸渊已生成解答)
+ const ticketStatus = type === 'syslog' ? '待处理' : '已完成';
+
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: '铸渊Agent·自动管道' } }] },
- '状态': { select: { name: '待处理' } },
+ '状态': { select: { name: ticketStatus } },
'优先级': { select: { name: 'P1' } }
},
children: [{
diff --git a/scripts/verify-modules.js b/scripts/verify-modules.js
index 807ec605..2df4ddf5 100644
--- a/scripts/verify-modules.js
+++ b/scripts/verify-modules.js
@@ -63,23 +63,32 @@ function extractModuleIds(content, broadcastId) {
// ══════════════════════════════════════════════════════════
function extractDevId(content, broadcastId) {
- // 从广播编号提取开发者后缀(如 BC-M22-009-AW → AW)
- var suffixMatch = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
- var devSuffix = suffixMatch ? suffixMatch[1] : '';
-
- // 从内容中提取 DEV-XXX
+ // 从内容中直接提取 DEV-XXX(最优先)
var devMatch = content.match(/\b(DEV-\d{3})\b/i);
if (devMatch) return devMatch[1].toUpperCase();
- // 从 dev-status.json 通过后缀查找
+ // 从广播编号提取开发者后缀(如 BC-M22-009-AW → AW)
+ var suffixMatch = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ var devSuffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
+
+ // 从 dev-status.json 通过后缀匹配开发者
if (devSuffix) {
try {
var devStatus = JSON.parse(fs.readFileSync(DEV_STATUS_PATH, 'utf8'));
var team = devStatus.team || [];
+ // 后缀缩写映射表(从 dev-status.json 中名字的拼音首字母)
+ var suffixMap = {
+ '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',
+ 'XX': 'DEV-013', 'SY': 'DEV-014',
+ };
+ if (suffixMap[devSuffix]) return suffixMap[devSuffix];
+
+ // 兜底:尝试从 team 中匹配 waiting 字段里的广播编号
for (var i = 0; i < team.length; i++) {
- var name = team[i].name || '';
- // 简单匹配:用名字首字母缩写匹配后缀
- if (name && devSuffix.length >= 2) {
+ var waiting = team[i].waiting || '';
+ if (waiting.includes(broadcastId) || waiting.includes(devSuffix)) {
return team[i].dev_id;
}
}
From e0dbe71bdc422eea859f1ec173c22e4f20ea92f9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:33:17 +0000
Subject: [PATCH 4/6] =?UTF-8?q?feat:=20Phase=20B=20=E5=85=A8=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E9=97=AD=E7=8E=AF=E5=AF=B9=E6=8E=A5=20=C2=B7=20?=
=?UTF-8?q?=E6=A0=87=E5=87=86=E5=8C=96=E5=B7=A5=E5=8D=95=20+=20=E4=BA=BA?=
=?UTF-8?q?=E6=A0=BC=E4=BD=93=E8=B0=83=E7=94=A8=E7=AB=AF=E7=82=B9=20+=20?=
=?UTF-8?q?=E5=BF=83=E8=B7=B3=E7=9B=91=E6=8E=A7=20+=20=E5=B9=BF=E6=92=AD?=
=?UTF-8?q?=E6=8E=A8=E9=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase B1: 标准化工单创建 (create-standardized-ticket.js + receive-syslog.js更新)
Phase B2: 人格体调用端点 (invoke-persona.js + persona-invoke.yml workflow_dispatch)
Phase B3: 心跳监控 (notion-heartbeat.js + notion-heartbeat.yml 每5分钟)
Phase B4: 广播推送 (push-broadcast-to-github.js + broadcasts/ 目录)
工作流更新: syslog-issue-pipeline.yml, syslog-auto-pipeline.yml, notion-callback-pipeline.yml
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.../workflows/notion-callback-pipeline.yml | 63 +++
.github/workflows/notion-heartbeat.yml | 44 +++
.github/workflows/persona-invoke.yml | 248 ++++++++++++
.github/workflows/syslog-auto-pipeline.yml | 100 +----
.github/workflows/syslog-issue-pipeline.yml | 100 +----
broadcasts/README.md | 22 ++
scripts/create-standardized-ticket.js | 197 ++++++++++
scripts/invoke-persona.js | 360 ++++++++++++++++++
scripts/notion-heartbeat.js | 355 +++++++++++++++++
scripts/push-broadcast-to-github.js | 179 +++++++++
scripts/receive-syslog.js | 26 +-
11 files changed, 1516 insertions(+), 178 deletions(-)
create mode 100644 .github/workflows/notion-heartbeat.yml
create mode 100644 .github/workflows/persona-invoke.yml
create mode 100644 broadcasts/README.md
create mode 100644 scripts/create-standardized-ticket.js
create mode 100644 scripts/invoke-persona.js
create mode 100644 scripts/notion-heartbeat.js
create mode 100644 scripts/push-broadcast-to-github.js
diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml
index a6fc2d39..463d7ced 100644
--- a/.github/workflows/notion-callback-pipeline.yml
+++ b/.github/workflows/notion-callback-pipeline.yml
@@ -109,6 +109,69 @@ jobs:
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 }}
diff --git a/.github/workflows/notion-heartbeat.yml b/.github/workflows/notion-heartbeat.yml
new file mode 100644
index 00000000..d7b5969a
--- /dev/null
+++ b/.github/workflows/notion-heartbeat.yml
@@ -0,0 +1,44 @@
+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
+
+ 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
diff --git a/.github/workflows/persona-invoke.yml b/.github/workflows/persona-invoke.yml
new file mode 100644
index 00000000..e2ab5d45
--- /dev/null
+++ b/.github/workflows/persona-invoke.yml
@@ -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();
+ "
diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml
index 02eb3e5e..a0d84b10 100644
--- a/.github/workflows/syslog-auto-pipeline.yml
+++ b/.github/workflows/syslog-auto-pipeline.yml
@@ -130,7 +130,8 @@ jobs:
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 }}
@@ -140,93 +141,18 @@ jobs:
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
- AUTHOR: ${{ steps.parse.outputs.author }}
- run: |
- node -e "
- const https = require('https');
+ 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)';
- const modulesUploaded = process.env.MODULES_UPLOADED || 'false';
- const moduleVerify = process.env.MODULE_VERIFY || '{}';
- const author = process.env.AUTHOR || 'unknown';
-
- if (!token || !dbId) {
- console.log('⚠️ Notion credentials not configured, skipping ticket creation');
- process.exit(0);
- }
-
- const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
- const verifyStatus = modulesUploaded === 'true' ? '模块已验证✅' : '模块待验证⚠️';
- const title = '[自动] ' + broadcastId + ' · ' + typeLabel + ' · ' + verifyStatus;
-
- // 构建工单内容:包含人格体处理结果 + 模块验证数据
- const ticketContent = [
- '## 铸渊核心大脑处理结果',
- '',
- result.slice(0, 1500),
- '',
- '## 模块上传验证',
- '',
- '模块全部上传: ' + (modulesUploaded === 'true' ? '✅ 是' : '❌ 否'),
- '提交者: ' + author,
- '',
- '验证详情: ' + moduleVerify.slice(0, 400),
- ].join('\\n');
-
- // SYSLOG类型工单设为'待处理'(等待Notion侧核心大脑处理)
- // 提问类型工单设为'已完成'(铸渊已生成解答)
- const ticketStatus = type === 'syslog' ? '待处理' : '已完成';
-
- 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: '铸渊Agent·自动管道' } }] },
- '状态': { select: { name: ticketStatus } },
- '优先级': { select: { name: 'P1' } }
- },
- children: [{
- object: 'block',
- type: 'paragraph',
- paragraph: {
- rich_text: [{ type: 'text', text: { content: ticketContent.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:
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index 4acbc3b0..27ac9cfc 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -130,7 +130,8 @@ jobs:
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 }}
@@ -140,93 +141,18 @@ jobs:
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
- AUTHOR: ${{ steps.parse.outputs.author }}
- run: |
- node -e "
- const https = require('https');
+ 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)';
- const modulesUploaded = process.env.MODULES_UPLOADED || 'false';
- const moduleVerify = process.env.MODULE_VERIFY || '{}';
- const author = process.env.AUTHOR || 'unknown';
-
- if (!token || !dbId) {
- console.log('⚠️ Notion credentials not configured, skipping ticket creation');
- process.exit(0);
- }
-
- const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
- const verifyStatus = modulesUploaded === 'true' ? '模块已验证✅' : '模块待验证⚠️';
- const title = '[自动] ' + broadcastId + ' · ' + typeLabel + ' · ' + verifyStatus;
-
- // 构建工单内容:包含人格体处理结果 + 模块验证数据
- const ticketContent = [
- '## 铸渊核心大脑处理结果',
- '',
- result.slice(0, 1500),
- '',
- '## 模块上传验证',
- '',
- '模块全部上传: ' + (modulesUploaded === 'true' ? '✅ 是' : '❌ 否'),
- '提交者: ' + author,
- '',
- '验证详情: ' + moduleVerify.slice(0, 400),
- ].join('\\n');
-
- // SYSLOG类型工单设为'待处理'(等待Notion侧核心大脑处理)
- // 提问类型工单设为'已完成'(铸渊已生成解答)
- const ticketStatus = type === 'syslog' ? '待处理' : '已完成';
-
- 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: '铸渊Agent·自动管道' } }] },
- '状态': { select: { name: ticketStatus } },
- '优先级': { select: { name: 'P1' } }
- },
- children: [{
- object: 'block',
- type: 'paragraph',
- paragraph: {
- rich_text: [{ type: 'text', text: { content: ticketContent.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:
diff --git a/broadcasts/README.md b/broadcasts/README.md
new file mode 100644
index 00000000..a1d6d7bf
--- /dev/null
+++ b/broadcasts/README.md
@@ -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 自动管理。
diff --git a/scripts/create-standardized-ticket.js b/scripts/create-standardized-ticket.js
new file mode 100644
index 00000000..a1607245
--- /dev/null
+++ b/scripts/create-standardized-ticket.js
@@ -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 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) {
+ var fs = require('fs');
+ 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();
diff --git a/scripts/invoke-persona.js b/scripts/invoke-persona.js
new file mode 100644
index 00000000..bd4cc12e
--- /dev/null
+++ b/scripts/invoke-persona.js
@@ -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');
+
+// ══════════════════════════════════════════════════════════
+// 配置
+// ══════════════════════════════════════════════════════════
+
+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 childProcess = require('child_process');
+ 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<= 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: [
+ '',
+ '
⚠️ 工单处理超时告警
',
+ '
以下工单超过 3 次重试仍未收到人格体回执:
',
+ '
' + ticketList + '
',
+ '
请登录 Notion 工单队列检查。
',
+ '
🌀 铸渊 · 心跳监控 · 自动告警
',
+ '
',
+ ].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);
+});
diff --git a/scripts/push-broadcast-to-github.js b/scripts/push-broadcast-to-github.js
new file mode 100644
index 00000000..32af6eb1
--- /dev/null
+++ b/scripts/push-broadcast-to-github.js
@@ -0,0 +1,179 @@
+// 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 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) {
+ // 从 BROADCAST_ID 提取后缀 → 映射到 DEV-XXX
+ var suffixMatch = BROADCAST_ID.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ var suffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
+ var suffixMap = {
+ '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',
+ 'XX': 'DEV-013', 'SY': 'DEV-014',
+ };
+ devId = suffixMap[suffix] || '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);
+});
diff --git a/scripts/receive-syslog.js b/scripts/receive-syslog.js
index dd136852..00ff57f0 100644
--- a/scripts/receive-syslog.js
+++ b/scripts/receive-syslog.js
@@ -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;
}
From c9d6495e5fb8d087d20f7e9dd5987c551f16e235 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:37:11 +0000
Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=E6=8F=90=E5=8F=96=E5=85=B1?=
=?UTF-8?q?=E4=BA=AB=20dev-suffix-map=20=E6=A8=A1=E5=9D=97=20+=20=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=E4=BB=A3=E7=A0=81=E5=AE=A1=E6=9F=A5=E5=8F=8D=E9=A6=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 创建 scripts/utils/dev-suffix-map.js (共享后缀→DEV-ID映射)
- 更新 verify-modules.js + push-broadcast-to-github.js 使用共享映射
- 移动 require 到文件顶部 (invoke-persona.js, create-standardized-ticket.js)
- 心跳监控参数可配置化 (HEARTBEAT_TIMEOUT_MINUTES, MAX_RETRY_COUNT)
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
scripts/create-standardized-ticket.js | 2 +-
scripts/invoke-persona.js | 2 +-
scripts/notion-heartbeat.js | 4 +--
scripts/push-broadcast-to-github.js | 12 ++-------
scripts/utils/dev-suffix-map.js | 38 +++++++++++++++++++++++++++
scripts/verify-modules.js | 18 +++++--------
6 files changed, 50 insertions(+), 26 deletions(-)
create mode 100644 scripts/utils/dev-suffix-map.js
diff --git a/scripts/create-standardized-ticket.js b/scripts/create-standardized-ticket.js
index a1607245..4bfb5b1f 100644
--- a/scripts/create-standardized-ticket.js
+++ b/scripts/create-standardized-ticket.js
@@ -18,6 +18,7 @@
'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 || '';
@@ -184,7 +185,6 @@ async function createStandardizedTicket() {
// 输出工单 ID 到 GITHUB_OUTPUT
var outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
- var fs = require('fs');
fs.appendFileSync(outputFile, 'ticket_page_id=' + result.id + '\n');
fs.appendFileSync(outputFile, 'ticket_url=' + (result.url || '') + '\n');
}
diff --git a/scripts/invoke-persona.js b/scripts/invoke-persona.js
index bd4cc12e..c88cbd5c 100644
--- a/scripts/invoke-persona.js
+++ b/scripts/invoke-persona.js
@@ -24,6 +24,7 @@ const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
+const childProcess = require('child_process');
// ══════════════════════════════════════════════════════════
// 配置
@@ -166,7 +167,6 @@ async function invokePersona(workOrder) {
process.env.AUTHOR = workOrder.developer;
// 执行 wake-persona.js(fork 子进程)
- var childProcess = require('child_process');
var wakeScript = path.join(__dirname, 'wake-persona.js');
return new Promise(function (resolve, reject) {
diff --git a/scripts/notion-heartbeat.js b/scripts/notion-heartbeat.js
index a259d4bd..5707647b 100644
--- a/scripts/notion-heartbeat.js
+++ b/scripts/notion-heartbeat.js
@@ -30,8 +30,8 @@ var SMTP_USER = process.env.SMTP_USER || '';
var SMTP_PASS = process.env.SMTP_PASS || '';
var NOTION_VERSION = '2022-06-28';
-var HEARTBEAT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
-var MAX_RETRIES = 3;
+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';
diff --git a/scripts/push-broadcast-to-github.js b/scripts/push-broadcast-to-github.js
index 32af6eb1..41cd164f 100644
--- a/scripts/push-broadcast-to-github.js
+++ b/scripts/push-broadcast-to-github.js
@@ -18,6 +18,7 @@
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 || '';
@@ -94,16 +95,7 @@ async function pushBroadcast() {
// 确定开发者 ID(从广播编号提取或使用环境变量)
var devId = DEVELOPER_ID;
if (!devId) {
- // 从 BROADCAST_ID 提取后缀 → 映射到 DEV-XXX
- var suffixMatch = BROADCAST_ID.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
- var suffix = suffixMatch ? suffixMatch[1].toUpperCase() : '';
- var suffixMap = {
- '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',
- 'XX': 'DEV-013', 'SY': 'DEV-014',
- };
- devId = suffixMap[suffix] || 'UNKNOWN';
+ devId = devSuffixMap.getDevIdFromBroadcast(BROADCAST_ID) || 'UNKNOWN';
}
// 构建路径和提交信息
diff --git a/scripts/utils/dev-suffix-map.js b/scripts/utils/dev-suffix-map.js
new file mode 100644
index 00000000..35248f80
--- /dev/null
+++ b/scripts/utils/dev-suffix-map.js
@@ -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,
+};
diff --git a/scripts/verify-modules.js b/scripts/verify-modules.js
index 2df4ddf5..24b250e5 100644
--- a/scripts/verify-modules.js
+++ b/scripts/verify-modules.js
@@ -17,6 +17,7 @@
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 || '';
@@ -67,25 +68,18 @@ function extractDevId(content, broadcastId) {
var devMatch = content.match(/\b(DEV-\d{3})\b/i);
if (devMatch) return devMatch[1].toUpperCase();
- // 从广播编号提取开发者后缀(如 BC-M22-009-AW → AW)
+ // 从广播编号提取开发者编号(使用共享映射表)
+ 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() : '';
- // 从 dev-status.json 通过后缀匹配开发者
if (devSuffix) {
try {
var devStatus = JSON.parse(fs.readFileSync(DEV_STATUS_PATH, 'utf8'));
var team = devStatus.team || [];
- // 后缀缩写映射表(从 dev-status.json 中名字的拼音首字母)
- var suffixMap = {
- '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',
- 'XX': 'DEV-013', 'SY': 'DEV-014',
- };
- if (suffixMap[devSuffix]) return suffixMap[devSuffix];
-
- // 兜底:尝试从 team 中匹配 waiting 字段里的广播编号
for (var i = 0; i < team.length; i++) {
var waiting = team[i].waiting || '';
if (waiting.includes(broadcastId) || waiting.includes(devSuffix)) {
From 1479da4e1cfe05cd5e09c086442ad7882653ff5e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:39:06 +0000
Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=20notion-heartbea?=
=?UTF-8?q?t.yml=20=E6=98=BE=E5=BC=8F=E6=9D=83=E9=99=90=E5=A3=B0=E6=98=8E?=
=?UTF-8?q?=EF=BC=88CodeQL=E5=AE=89=E5=85=A8=E4=BF=AE=E5=A4=8D=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/notion-heartbeat.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/notion-heartbeat.yml b/.github/workflows/notion-heartbeat.yml
index d7b5969a..76653dc0 100644
--- a/.github/workflows/notion-heartbeat.yml
+++ b/.github/workflows/notion-heartbeat.yml
@@ -20,6 +20,9 @@ jobs:
heartbeat:
name: 💓 工单心跳检测
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ actions: write
steps:
- name: Checkout