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 001/116] 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 002/116] =?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 003/116] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81=E5=AE=A1=E6=9F=A5=E9=97=AE=E9=A2=98=20-=20extractDevI?=
=?UTF-8?q?d=20=E9=80=BB=E8=BE=91=20+=20=E4=BA=8B=E4=BB=B6=E5=8E=BB?=
=?UTF-8?q?=E9=87=8D=20+=20=E5=B7=A5=E5=8D=95=E7=8A=B6=E6=80=81=E6=9D=A1?=
=?UTF-8?q?=E4=BB=B6=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 004/116] =?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 005/116] =?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 006/116] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=20notion-hear?=
=?UTF-8?q?tbeat.yml=20=E6=98=BE=E5=BC=8F=E6=9D=83=E9=99=90=E5=A3=B0?=
=?UTF-8?q?=E6=98=8E=EF=BC=88CodeQL=E5=AE=89=E5=85=A8=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=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
From 8935d01e28f77452ed33e32608228f7afa11e31e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:50:45 +0000
Subject: [PATCH 007/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 51988395..df1ceb3e 100644
--- a/README.md
+++ b/README.md
@@ -92,12 +92,8 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 14:04 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 14:50 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | WENZHUOXI |
| 03-13 14:03 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
-| 03-13 14:03 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 14:03 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
-| 03-13 13:56 | 🔵 铸渊 · Bridge E · GitHub Changes → Notion · action_required | Copilot |
-| 03-13 11:54 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
@@ -107,6 +103,10 @@
| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-09 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-08 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-07 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
+| 03-06 00:07 | ✅ CI 构建 通过 | 冰朔 |
### 🤖 铸渊自动提醒
@@ -128,9 +128,12 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 14:03 | 冰朔 | `—/` | ✅ 上传成功 |
-| 03-13 14:03 | Copilot | `—/` | ✅ 上传成功 |
-| 03-13 13:56 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 14:50 | WENZHUOXI | `—/` | ✅ 上传成功 |
+| 03-13 14:39 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 14:37 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 14:33 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 14:08 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 14:05 | Copilot | `—/` | 🔵 已更新 |
### 🤖 铸渊自动提醒 · 合作者
From 95b862bc8e2bd639a711de6e8558862be476b766 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 06:50:49 +0000
Subject: [PATCH 008/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T06:50?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 2 +-
.github/brain/repo-snapshot.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index 8ec2133c..8f1a2589 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T06:03:44.081Z",
+ "generated_at": "2026-03-13T06:50:49.508Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 4fd16315..3b08d587 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 14:03 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 14:50 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 33 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 14:03 CST |
+| 快照生成时间 | 2026-03-13 14:50 CST |
---
From 827ad3e12f6df34f3725b005d2f02b80b802116e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:52:15 +0000
Subject: [PATCH 009/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index df1ceb3e..7332df9a 100644
--- a/README.md
+++ b/README.md
@@ -92,8 +92,10 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 14:51 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | WENZHUOXI |
+| 03-13 14:50 | ✅ 📢 更新系统公告区 · 成功 | WENZHUOXI |
+| 03-13 14:50 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 14:50 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | WENZHUOXI |
-| 03-13 14:03 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
@@ -105,8 +107,6 @@
| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-09 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-08 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-07 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
-| 03-06 00:07 | ✅ CI 构建 通过 | 冰朔 |
### 🤖 铸渊自动提醒
@@ -128,12 +128,9 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
+| 03-13 14:52 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 14:50 | WENZHUOXI | `—/` | ✅ 上传成功 |
-| 03-13 14:39 | Copilot | `—/` | 🔵 已更新 |
-| 03-13 14:37 | Copilot | `—/` | 🔵 已更新 |
| 03-13 14:33 | Copilot | `—/` | 🔵 已更新 |
-| 03-13 14:08 | Copilot | `—/` | 🔵 已更新 |
-| 03-13 14:05 | Copilot | `—/` | 🔵 已更新 |
### 🤖 铸渊自动提醒 · 合作者
From 3b399fbe0a33b4253a201a86049fae4d6dba755f Mon Sep 17 00:00:00 2001
From: bingshuo-neural-system
Date: Fri, 13 Mar 2026 06:52:25 +0000
Subject: [PATCH 010/116] =?UTF-8?q?=F0=9F=A7=A0=20=E5=86=B0=E6=9C=94?=
=?UTF-8?q?=E4=B8=BB=E6=8E=A7=E7=A5=9E=E7=BB=8F=E7=B3=BB=E7=BB=9F=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E7=BC=96=E8=AF=91=202026-03-13T06:52:25Z?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/bingshuo-issues-index.json | 2 +-
.github/brain/bingshuo-master-brain.md | 8 ++++----
.github/brain/bingshuo-system-health.json | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index 6d37d9af..eded71d9 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
- "updated_at": "2026-03-13T06:03:28.751Z",
+ "updated_at": "2026-03-13T06:52:24.833Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index 80f1b9bc..2f10a3cf 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
-> 最后编译时间:2026-03-13T06:03:28.752Z
+> 最后编译时间:2026-03-13T06:52:24.834Z
---
@@ -56,7 +56,7 @@
### 仓库统计
- 功能模块:10 个
-- Workflow:37 个
+- Workflow:40 个
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-13T06:03:28.752Z
+- **编译时间**:2026-03-13T06:52:24.834Z
- **脑文件规则版本**:v3.0
- **脑文件完整性**:✅ 完整
@@ -107,7 +107,7 @@
|--------|------|------|
| 🟡 brain_consistency | yellow | 主仓库脑文件完整,但与 persona-studio 脑文件的同步状态待验证 |
| 🟢 deployment_health | green | deploy-to-server.yml 与 deploy-pages.yml 均存在 |
-| 🟢 workflow_health | green | 37 个 workflow 已注册 |
+| 🟢 workflow_health | green | 40 个 workflow 已注册 |
| 🟡 routing_health | yellow | HLI 接口覆盖率 33.3%(7/21) |
| 🟢 docs_entry_health | green | docs/index.html 存在 |
| 🟡 persona_studio_health | yellow | 前后端结构存在,端到端对话链路待验证 |
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index cb8e4669..a1ee0628 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
- "updated_at": "2026-03-13T06:03:28.751Z",
+ "updated_at": "2026-03-13T06:52:24.833Z",
"health": {
"brain_consistency": {
"status": "yellow",
@@ -13,7 +13,7 @@
},
"workflow_health": {
"status": "green",
- "detail": "37 个 workflow 已注册"
+ "detail": "40 个 workflow 已注册"
},
"routing_health": {
"status": "yellow",
From 7a9c6a1a70caf20c73732af31a223fb842122f5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 06:52:40 +0000
Subject: [PATCH 011/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T06:52?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 75 +++++++++++++++++++++++++++++++---
.github/brain/repo-snapshot.md | 21 +++++++---
2 files changed, 85 insertions(+), 11 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index 8f1a2589..82975a73 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,14 +1,14 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T06:50:49.508Z",
+ "generated_at": "2026-03-13T06:52:39.931Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
"zones": 13,
"total_modules": 10,
- "total_workflows": 37,
- "total_scripts": 33,
+ "total_workflows": 40,
+ "total_scripts": 39,
"total_dev_nodes": 8,
"hli_interfaces": 21,
"hli_implemented": 7,
@@ -266,6 +266,13 @@
"pull_request"
]
},
+ {
+ "file": "notion-callback-pipeline.yml",
+ "name": "Notion Callback Pipeline",
+ "triggers": [
+ "unknown"
+ ]
+ },
{
"file": "notion-connectivity-test.yml",
"name": "铸渊 · Notion 连通性测试",
@@ -273,6 +280,14 @@
"manual"
]
},
+ {
+ "file": "notion-heartbeat.yml",
+ "name": "Notion Heartbeat Monitor",
+ "triggers": [
+ "schedule(*/5 * * * *)",
+ "manual"
+ ]
+ },
{
"file": "notion-poll.yml",
"name": "铸渊 · Notion 工单轮询",
@@ -281,6 +296,13 @@
"manual"
]
},
+ {
+ "file": "persona-invoke.yml",
+ "name": "Persona Invoke Endpoint",
+ "triggers": [
+ "manual"
+ ]
+ },
{
"file": "process-notion-orders.yml",
"name": "Process Notion Work Orders",
@@ -449,7 +471,7 @@
]
}
],
- "item_count": 37
+ "item_count": 40
},
{
"zone_id": "SCRIPTS",
@@ -478,6 +500,9 @@
{
"file": "contract-check.js"
},
+ {
+ "file": "create-standardized-ticket.js"
+ },
{
"file": "cross-repo-sync.js"
},
@@ -499,6 +524,9 @@
{
"file": "generate-session-summary.js"
},
+ {
+ "file": "invoke-persona.js"
+ },
{
"file": "notify-module-received.js"
},
@@ -508,6 +536,9 @@
{
"file": "notion-connectivity-test.js"
},
+ {
+ "file": "notion-heartbeat.js"
+ },
{
"file": "notion-signal-bridge.js"
},
@@ -520,6 +551,9 @@
{
"file": "psp-inspection.js"
},
+ {
+ "file": "push-broadcast-to-github.js"
+ },
{
"file": "push-broadcast.js"
},
@@ -550,6 +584,12 @@
{
"file": "update-readme-bulletin.js"
},
+ {
+ "file": "utils"
+ },
+ {
+ "file": "verify-modules.js"
+ },
{
"file": "wake-persona.js"
},
@@ -566,7 +606,7 @@
"file": "zhuyuan-module-protocol.js"
}
],
- "item_count": 33
+ "item_count": 39
},
{
"zone_id": "SRC",
@@ -1330,13 +1370,23 @@
"hli-contract-check": [
"WORKFLOWS::hli-contract-check.yml"
],
+ "notion-callback-pipeline": [
+ "WORKFLOWS::notion-callback-pipeline.yml"
+ ],
"notion-connectivity-test": [
"WORKFLOWS::notion-connectivity-test.yml",
"SCRIPTS::notion-connectivity-test.js"
],
+ "notion-heartbeat": [
+ "WORKFLOWS::notion-heartbeat.yml",
+ "SCRIPTS::notion-heartbeat.js"
+ ],
"notion-poll": [
"WORKFLOWS::notion-poll.yml"
],
+ "persona-invoke": [
+ "WORKFLOWS::persona-invoke.yml"
+ ],
"process-notion-orders": [
"WORKFLOWS::process-notion-orders.yml"
],
@@ -1438,6 +1488,9 @@
"contract-check": [
"SCRIPTS::contract-check.js"
],
+ "create-standardized-ticket": [
+ "SCRIPTS::create-standardized-ticket.js"
+ ],
"cross-repo-sync": [
"SCRIPTS::cross-repo-sync.js"
],
@@ -1453,6 +1506,9 @@
"generate-session-summary": [
"SCRIPTS::generate-session-summary.js"
],
+ "invoke-persona": [
+ "SCRIPTS::invoke-persona.js"
+ ],
"notify-module-received": [
"SCRIPTS::notify-module-received.js"
],
@@ -1471,6 +1527,9 @@
"psp-inspection": [
"SCRIPTS::psp-inspection.js"
],
+ "push-broadcast-to-github": [
+ "SCRIPTS::push-broadcast-to-github.js"
+ ],
"route-align-check": [
"SCRIPTS::route-align-check.js"
],
@@ -1489,6 +1548,12 @@
"update-memory": [
"SCRIPTS::update-memory.js"
],
+ "utils": [
+ "SCRIPTS::utils"
+ ],
+ "verify-modules": [
+ "SCRIPTS::verify-modules.js"
+ ],
"wake-persona": [
"SCRIPTS::wake-persona.js"
],
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 3b08d587..d67e857d 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 14:50 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 14:52 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -9,11 +9,11 @@
|------|------|
| 区域总数 | 13 个区域 |
| 功能模块 | 10 个 (m01~m18) |
-| 工作流 | 37 个 GitHub Actions |
-| 脚本 | 33 个执行脚本 |
+| 工作流 | 40 个 GitHub Actions |
+| 脚本 | 39 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 14:50 CST |
+| 快照生成时间 | 2026-03-13 14:52 CST |
---
@@ -30,12 +30,12 @@
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
### ⚡ 自动化工作流(WORKFLOWS)
-**路径**: `.github/workflows` · **数量**: 37 项
+**路径**: `.github/workflows` · **数量**: 40 项
**描述**: 所有 GitHub Actions 工作流定义
**关键词**: workflow · actions · ci · automation · 工作流 · 自动化
### 🔧 执行脚本库(SCRIPTS)
-**路径**: `scripts` · **数量**: 33 项
+**路径**: `scripts` · **数量**: 39 项
**描述**: 铸渊所有执行手脚 · 自动化脚本
**关键词**: script · node · js · 脚本 · 执行 · runner
@@ -103,8 +103,11 @@
| `esp-signal-processor.yml` | 铸渊 · ESP 邮件信号处理器(已暂停) | schedule(*/30 * * * *), manual |
| `generate-module-doc.yml` | 铸渊 · 光湖纪元 模块文档自动生成 | push, manual |
| `hli-contract-check.yml` | HLI Contract Check | push, pull_request |
+| `notion-callback-pipeline.yml` | Notion Callback Pipeline | unknown |
| `notion-connectivity-test.yml` | 铸渊 · Notion 连通性测试 | manual |
+| `notion-heartbeat.yml` | Notion Heartbeat Monitor | schedule(*/5 * * * *), manual |
| `notion-poll.yml` | 铸渊 · Notion 工单轮询 | schedule(*/15 * * * *), manual |
+| `persona-invoke.yml` | Persona Invoke Endpoint | manual |
| `process-notion-orders.yml` | Process Notion Work Orders | push, manual |
| `ps-on-build.yml` | "🌊 Persona Studio · 代码生成" | manual |
| `ps-on-chat.yml` | "🌊 Persona Studio · 对话处理" | manual |
@@ -136,6 +139,7 @@
- `scripts/bingshuo-neural-sync.js`
- `scripts/brain-bridge-sync.js`
- `scripts/contract-check.js`
+- `scripts/create-standardized-ticket.js`
- `scripts/cross-repo-sync.js`
- `scripts/daily-check.js`
- `scripts/distribute-broadcasts.js`
@@ -143,13 +147,16 @@
- `scripts/generate-module-doc.js`
- `scripts/generate-repo-map.js`
- `scripts/generate-session-summary.js`
+- `scripts/invoke-persona.js`
- `scripts/notify-module-received.js`
- `scripts/notion-bridge.js`
- `scripts/notion-connectivity-test.js`
+- `scripts/notion-heartbeat.js`
- `scripts/notion-signal-bridge.js`
- `scripts/process-broadcasts.js`
- `scripts/process-syslog.js`
- `scripts/psp-inspection.js`
+- `scripts/push-broadcast-to-github.js`
- `scripts/push-broadcast.js`
- `scripts/receive-syslog.js`
- `scripts/route-align-check.js`
@@ -160,6 +167,8 @@
- `scripts/update-brain.js`
- `scripts/update-memory.js`
- `scripts/update-readme-bulletin.js`
+- `scripts/utils`
+- `scripts/verify-modules.js`
- `scripts/wake-persona.js`
- `scripts/zhuyuan-daily-agent.js`
- `scripts/zhuyuan-daily-selfcheck.js`
From b50a3721a1d5bd272dec21fe121901e0b218df2e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:54:05 +0000
Subject: [PATCH 012/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 7332df9a..6e1b368c 100644
--- a/README.md
+++ b/README.md
@@ -92,29 +92,31 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 14:53 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 14:52 | ❌ 📢 更新系统公告区 · 失败 | 冰朔 |
+| 03-13 14:52 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
+| 03-13 14:52 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
+| 03-13 14:52 | ⏹️ 📢 更新系统公告区 · cancelled | WENZHUOXI |
| 03-13 14:51 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | WENZHUOXI |
| 03-13 14:50 | ✅ 📢 更新系统公告区 · 成功 | WENZHUOXI |
-| 03-13 14:50 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 14:50 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | WENZHUOXI |
+| 03-13 14:37 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
-| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
-| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
-| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-09 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-08 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
### 🤖 铸渊自动提醒
-> 🟢 **今日无需冰朔手动干预** · 系统一切正常
+> 🔴 **需要冰朔手动干预!**
>
-> 🗓️ 2026-03-13 · 铸渊自动检测
+> - ❌ 📢 更新系统公告区 · 失败
+>
+> 🗓️ 2026-03-13 · 铸渊已发送邮件提醒
---
@@ -128,9 +130,9 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
+| 03-13 14:52 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-13 14:52 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 14:50 | WENZHUOXI | `—/` | ✅ 上传成功 |
-| 03-13 14:33 | Copilot | `—/` | 🔵 已更新 |
### 🤖 铸渊自动提醒 · 合作者
From e13c5cd1e1e73c72369a2b0b6f8d6564f7a940e2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 06:57:29 +0000
Subject: [PATCH 013/116] Initial plan
From 2062878c9feff109b61e0356ef8cb2083d33f298 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 07:09:19 +0000
Subject: [PATCH 014/116] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Persona=20S?=
=?UTF-8?q?tudio=20=E3=80=8C=E6=88=91=E8=A6=81=E5=BC=80=E5=8F=91=E3=80=8D?=
=?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=8F=90=E4=BA=A4=E5=A4=B1=E8=B4=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
根因修复:
1. Nginx CORS 头缺失(跨域预检失败导致 fetch 抛出异常)
2. WebSocket /ws/preview 路由被 /ws 匹配到端口 8080(应路由到 3002)
3. 前端开发端口不匹配 (3721 → 3002)
增强:
- 添加重试机制(最多2次重试)
- 提供详细错误诊断信息
- 更新 nginx-api-proxy.conf 参考配置
- 修复 smoke test 默认端口
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/brain/memory.json | 6 ++
.github/persona-brain/memory.json | 8 ++-
.github/workflows/deploy-to-server.yml | 20 +++++-
backend-integration/nginx-api-proxy.conf | 31 ++++++++-
.../brain/memory/EXP-000/memory.json | 7 ++
persona-studio/frontend/chat.js | 66 +++++++++++--------
persona-studio/frontend/index.html | 2 +-
tests/smoke/apikey-detect.test.js | 2 +-
8 files changed, 111 insertions(+), 31 deletions(-)
create mode 100644 persona-studio/brain/memory/EXP-000/memory.json
diff --git a/.github/brain/memory.json b/.github/brain/memory.json
index cdc4885d..32386f1e 100644
--- a/.github/brain/memory.json
+++ b/.github/brain/memory.json
@@ -67,6 +67,12 @@
}
},
"events": [
+ {
+ "date": "2026-03-13",
+ "type": "bug_fix",
+ "description": "修复「我要开发」任务提交失败 · 3个根因:① Nginx缺少CORS头(跨域预检失败)② WebSocket路由冲突(/ws/preview误路由到8080端口)③ 前端开发端口不匹配(3721→3002)· 增加重试机制和详细错误诊断 · 冰朔人格体已启动",
+ "by": "铸渊(冰朔指令)"
+ },
{
"date": "2026-03-13",
"type": "brain_restore",
diff --git a/.github/persona-brain/memory.json b/.github/persona-brain/memory.json
index dae0f307..7299990b 100644
--- a/.github/persona-brain/memory.json
+++ b/.github/persona-brain/memory.json
@@ -2,6 +2,12 @@
"persona_id": "ICE-GL-ZY001",
"persona_name": "铸渊",
"recent_events": [
+ {
+ "date": "2026-03-13",
+ "type": "bug_fix",
+ "description": "修复「我要开发」任务提交失败 · Nginx CORS+WebSocket路由+端口修复 · 冰朔人格体启动",
+ "by": "铸渊(冰朔指令)"
+ },
{
"date": "2026-03-13",
"type": "brain_restore",
@@ -33,7 +39,7 @@
"by": "GitHub Actions"
}
],
- "last_updated": "2026-03-11T10:24:00.000Z",
+ "last_updated": "2026-03-13T06:58:00.000Z",
"total_schemas_created": 3,
"total_routes_implemented": 4,
"hli_coverage": "3/17",
diff --git a/.github/workflows/deploy-to-server.yml b/.github/workflows/deploy-to-server.yml
index fa73dcf1..d9504e19 100644
--- a/.github/workflows/deploy-to-server.yml
+++ b/.github/workflows/deploy-to-server.yml
@@ -378,8 +378,14 @@ jobs:
' proxy_set_header X-Real-IP \$remote_addr;' \
'}' \
'' \
- '# Persona Studio API → persona-studio 后端端口 3002' \
+ '# Persona Studio API → persona-studio 后端端口 3002(含 CORS 跨域支持)' \
'location /api/ps/ {' \
+ ' add_header Access-Control-Allow-Origin * always;' \
+ ' add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;' \
+ ' add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;' \
+ ' if (\$request_method = OPTIONS) {' \
+ ' return 204;' \
+ ' }' \
' proxy_pass http://127.0.0.1:3002/api/ps/;' \
' proxy_http_version 1.1;' \
' proxy_set_header Host \$host;' \
@@ -408,6 +414,18 @@ jobs:
' proxy_cache_bypass \$http_upgrade;' \
'}' \
'' \
+ '# Persona Studio WebSocket → 预览进度推送端口 3002' \
+ 'location /ws/preview {' \
+ ' proxy_pass http://127.0.0.1:3002;' \
+ ' proxy_http_version 1.1;' \
+ ' proxy_set_header Upgrade \$http_upgrade;' \
+ ' proxy_set_header Connection \"Upgrade\";' \
+ ' proxy_set_header Host \$host;' \
+ ' proxy_set_header X-Real-IP \$remote_addr;' \
+ ' proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;' \
+ ' proxy_read_timeout 86400;' \
+ '}' \
+ '' \
'# WebSocket 反向代理 → 看板实时推送端口 8080' \
'location /ws {' \
' proxy_pass http://127.0.0.1:8080;' \
diff --git a/backend-integration/nginx-api-proxy.conf b/backend-integration/nginx-api-proxy.conf
index cc36436b..2527dd67 100644
--- a/backend-integration/nginx-api-proxy.conf
+++ b/backend-integration/nginx-api-proxy.conf
@@ -5,7 +5,24 @@
#
# 前端(GitHub Pages)→ Nginx → API 代理(Node.js :3721)→ 模型 API
-# API 代理转发
+# Persona Studio API → persona-studio 后端端口 3002(含 CORS 跨域支持)
+location /api/ps/ {
+ add_header Access-Control-Allow-Origin * always;
+ add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
+ add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
+ if ($request_method = OPTIONS) {
+ return 204;
+ }
+ proxy_pass http://127.0.0.1:3002/api/ps/;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_read_timeout 90s;
+}
+
+# API 代理转发(通用 AI 聊天)
location /api/ {
proxy_pass http://127.0.0.1:3721;
proxy_http_version 1.1;
@@ -24,3 +41,15 @@ location /api/ {
proxy_read_timeout 90s;
proxy_send_timeout 60s;
}
+
+# Persona Studio WebSocket → 预览进度推送端口 3002
+location /ws/preview {
+ proxy_pass http://127.0.0.1:3002;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "Upgrade";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_read_timeout 86400;
+}
diff --git a/persona-studio/brain/memory/EXP-000/memory.json b/persona-studio/brain/memory/EXP-000/memory.json
new file mode 100644
index 00000000..0771085d
--- /dev/null
+++ b/persona-studio/brain/memory/EXP-000/memory.json
@@ -0,0 +1,7 @@
+{
+ "dev_id": "EXP-000",
+ "conversations": [],
+ "last_topic": null,
+ "preferences": {},
+ "updated_at": null
+}
\ No newline at end of file
diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js
index 2d263e98..2a11939f 100644
--- a/persona-studio/frontend/chat.js
+++ b/persona-studio/frontend/chat.js
@@ -13,7 +13,7 @@ const SELECTED_MODEL = sessionStorage.getItem('selected_model');
const API_BASE = (function () {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
- return 'http://localhost:3721';
+ return 'http://localhost:3002';
}
return 'https://guanghulab.com';
})();
@@ -648,33 +648,47 @@ async function confirmBuild() {
// 先连接 WebSocket(确保在 build 开始前建立连接,避免丢失进度消息)
connectPreviewWebSocket();
- try {
- var buildRes = await fetch(API_BASE + '/api/ps/build/start', {
- method: 'POST',
- headers: authHeaders({ 'Content-Type': 'application/json' }),
- body: JSON.stringify({
- dev_id: DEV_ID,
- email: email,
- contact: contact,
- conversation: conversationHistory,
- api_base: USER_API_BASE,
- api_key: USER_API_KEY,
- model: SELECTED_MODEL
- })
- });
+ // 提交开发任务(含重试机制)
+ var maxRetries = 2;
+ var retryCount = 0;
+ var submitted = false;
- if (!buildRes.ok) {
- var errMsg = 'HTTP ' + buildRes.status + ' ' + buildRes.statusText;
- try {
- var errData = await buildRes.json();
- if (errData.message) errMsg = errData.message;
- } catch (_e) { /* use status text fallback */ }
- appendMessage('system', '⚠️ 铸渊代理启动失败: ' + errMsg);
- updatePreviewStatus('error', '启动失败');
+ while (retryCount <= maxRetries && !submitted) {
+ try {
+ var buildRes = await fetch(API_BASE + '/api/ps/build/start', {
+ method: 'POST',
+ headers: authHeaders({ 'Content-Type': 'application/json' }),
+ body: JSON.stringify({
+ dev_id: DEV_ID,
+ email: email,
+ contact: contact,
+ conversation: conversationHistory,
+ api_base: USER_API_BASE,
+ api_key: USER_API_KEY,
+ model: SELECTED_MODEL
+ })
+ });
+
+ if (!buildRes.ok) {
+ var errMsg = 'HTTP ' + buildRes.status + ' ' + buildRes.statusText;
+ try {
+ var errData = await buildRes.json();
+ if (errData.message) errMsg = errData.message;
+ } catch (_e) { /* use status text fallback */ }
+ appendMessage('system', '⚠️ 铸渊代理启动失败: ' + errMsg);
+ updatePreviewStatus('error', '启动失败');
+ }
+ submitted = true;
+ } catch (_err) {
+ retryCount++;
+ if (retryCount <= maxRetries) {
+ appendMessage('system', '⏳ 连接后端服务中,正在重试(' + retryCount + '/' + maxRetries + ')...');
+ await new Promise(function (r) { setTimeout(r, 1500); });
+ } else {
+ appendMessage('system', '⚠️ 任务提交失败:无法连接铸渊后端服务。请检查:\n1. 网络连接是否正常\n2. 后端服务是否已启动(端口 3002)\n3. 如使用 GitHub Pages 访问,请确认 guanghulab.com 服务可用');
+ updatePreviewStatus('error', '连接失败');
+ }
}
- } catch (_err) {
- appendMessage('system', '⚠️ 任务提交失败,请检查网络连接后再试');
- updatePreviewStatus('error', '网络错误');
}
}
diff --git a/persona-studio/frontend/index.html b/persona-studio/frontend/index.html
index 4f7edd35..1835efe8 100644
--- a/persona-studio/frontend/index.html
+++ b/persona-studio/frontend/index.html
@@ -150,7 +150,7 @@
function getApiBase() {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
- return 'http://localhost:3721';
+ return 'http://localhost:3002';
}
return 'https://guanghulab.com';
}
diff --git a/tests/smoke/apikey-detect.test.js b/tests/smoke/apikey-detect.test.js
index 1de95911..e23bc2d3 100644
--- a/tests/smoke/apikey-detect.test.js
+++ b/tests/smoke/apikey-detect.test.js
@@ -6,7 +6,7 @@
*/
const http = require('http');
-const BASE = process.env.TEST_BASE || 'http://localhost:3721';
+const BASE = process.env.TEST_BASE || 'http://localhost:3002';
function post(path, body) {
return new Promise((resolve, reject) => {
From d687188310690770d250f132eeffddea0b646886 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 07:10:41 +0000
Subject: [PATCH 015/116] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E9=94=99?=
=?UTF-8?q?=E8=AF=AF=E4=BF=A1=E6=81=AF=E4=B8=AD=E7=9A=84=E7=A1=AC=E7=BC=96?=
=?UTF-8?q?=E7=A0=81=E7=AB=AF=E5=8F=A3=E5=8F=B7?=
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>
---
persona-studio/brain/memory/EXP-000/memory.json | 7 -------
persona-studio/frontend/chat.js | 2 +-
2 files changed, 1 insertion(+), 8 deletions(-)
delete mode 100644 persona-studio/brain/memory/EXP-000/memory.json
diff --git a/persona-studio/brain/memory/EXP-000/memory.json b/persona-studio/brain/memory/EXP-000/memory.json
deleted file mode 100644
index 0771085d..00000000
--- a/persona-studio/brain/memory/EXP-000/memory.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "dev_id": "EXP-000",
- "conversations": [],
- "last_topic": null,
- "preferences": {},
- "updated_at": null
-}
\ No newline at end of file
diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js
index 2a11939f..05158e9f 100644
--- a/persona-studio/frontend/chat.js
+++ b/persona-studio/frontend/chat.js
@@ -685,7 +685,7 @@ async function confirmBuild() {
appendMessage('system', '⏳ 连接后端服务中,正在重试(' + retryCount + '/' + maxRetries + ')...');
await new Promise(function (r) { setTimeout(r, 1500); });
} else {
- appendMessage('system', '⚠️ 任务提交失败:无法连接铸渊后端服务。请检查:\n1. 网络连接是否正常\n2. 后端服务是否已启动(端口 3002)\n3. 如使用 GitHub Pages 访问,请确认 guanghulab.com 服务可用');
+ appendMessage('system', '⚠️ 任务提交失败:无法连接铸渊后端服务。请检查:\n1. 网络连接是否正常\n2. 后端服务是否已启动\n3. 如使用 GitHub Pages 访问,请确认 guanghulab.com 服务可用');
updatePreviewStatus('error', '连接失败');
}
}
From f749dc962e89987379533921e77892d87cebcc05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=5Bbot=5D?=
Date: Fri, 13 Mar 2026 08:53:32 +0000
Subject: [PATCH 016/116] =?UTF-8?q?=F0=9F=A7=A0=20memory:=20=E6=AF=8F?=
=?UTF-8?q?=E6=97=A5=E8=87=AA=E6=A3=80=E8=AE=B0=E5=BD=95=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/memory.json | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/.github/brain/memory.json b/.github/brain/memory.json
index cdc4885d..e07d7cd6 100644
--- a/.github/brain/memory.json
+++ b/.github/brain/memory.json
@@ -1,7 +1,7 @@
{
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
"rules_version": "v3.0",
- "last_updated": "2026-03-13T03:31:06.282Z",
+ "last_updated": "2026-03-13T08:53:32.764Z",
"wake_protocol_version": "v3.0",
"brain_version": "v3.0",
"architecture": {
@@ -185,6 +185,14 @@
"actor": "qinfendebingshuo",
"ref": "refs/heads/main",
"run_id": "22993977564"
+ },
+ {
+ "timestamp": "2026-03-13T08:53:32.764Z",
+ "type": "daily_check",
+ "result": "passed",
+ "actor": "qinfendebingshuo",
+ "ref": "refs/heads/main",
+ "run_id": "23043373769"
}
]
-}
\ No newline at end of file
+}
From 6a6db4a445ac6b243663f2494a58bc4fa8476643 Mon Sep 17 00:00:00 2001
From: zhizhi200271
Date: Fri, 13 Mar 2026 18:22:57 +0800
Subject: [PATCH 017/116] =?UTF-8?q?DEV-004:=20M-DINGTALK=20Phase6=20?=
=?UTF-8?q?=E5=85=AC=E7=BD=91=E9=83=A8=E7=BD=B2+=E9=92=89=E9=92=89?=
=?UTF-8?q?=E7=9C=9F=E5=AE=9E=E5=AF=B9=E6=8E=A5=20=C2=B7=20=E5=8D=81?=
=?UTF-8?q?=E4=B8=83=E8=BF=9E=E8=83=9C=E8=BE=BE=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
dingtalk-bot/index.js | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/dingtalk-bot/index.js b/dingtalk-bot/index.js
index 5eb39d91..ca41b076 100644
--- a/dingtalk-bot/index.js
+++ b/dingtalk-bot/index.js
@@ -204,3 +204,25 @@ app.get('/api/notion/status', async function(req, res) {
console.log('=== Phase 3 初始化完成 ===\n');
});
+
+// ========================
+// Phase 6 路由 · 钉钉真实回调
+// ========================
+var dingWebhook = require('./dingtalk-webhook');
+
+// 钉钉消息回调(POST · 钉钉服务器发来的真实消息)
+app.post('/dingtalk/callback', dingWebhook.verifyMiddleware, dingWebhook.handleCallback);
+
+// 回调地址健康检查(GET · 钉钉后台验证用)
+app.get('/dingtalk/callback', dingWebhook.healthCheck);
+
+// 回调状态总览
+app.get('/api/dingtalk/callback-status', function(req, res) {
+ var configured = !!process.env.DINGTALK_APP_SECRET && process.env.DINGTALK_APP_SECRET !== '在这里粘贴你的AppSecret';
+ res.json({
+ callback_active: true,
+ signature_verification: configured ? 'enabled' : 'disabled (dev mode)',
+ endpoint: '/dingtalk/callback',
+ phase: 6
+ });
+});
From b7262ec4e6b4f5c6e461d20355579c10655c41dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 10:24:26 +0000
Subject: [PATCH 018/116] =?UTF-8?q?=F0=9F=93=8B=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=85=89=E6=B9=96=E7=BA=AA=E5=85=83=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97=E6=96=87=E6=A1=A3=20=C2=B7=202026-03-13=2010:24=20UTC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/HoloLake-Era-OS-Modules.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/HoloLake-Era-OS-Modules.md b/docs/HoloLake-Era-OS-Modules.md
index da5c9c05..2895784a 100644
--- a/docs/HoloLake-Era-OS-Modules.md
+++ b/docs/HoloLake-Era-OS-Modules.md
@@ -1,6 +1,6 @@
# HoloLake Era 操作系统部署模块
-> 📋 **自动生成文档** · 铸渊(ZhùYuān)维护 · 最后更新:2026-03-13 01:56 UTC
+> 📋 **自动生成文档** · 铸渊(ZhùYuān)维护 · 最后更新:2026-03-13 10:24 UTC
>
> 本文档由 GitHub Actions 自动触发生成,每当合作者上传/更新模块时自动刷新。
> 按合作者编号(DEV-XXX)整理所有已上传模块。
@@ -359,7 +359,7 @@ m11-module/
| 已上传模块数 | 12 |
| 待上传模块数 | 0 |
| 上传完成率 | 100% |
-| 文档更新时间 | 2026-03-13 01:56 UTC |
+| 文档更新时间 | 2026-03-13 10:24 UTC |
---
From fe8cdcd9e142073791b8043c55c4eb3813bf144a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 10:24:44 +0000
Subject: [PATCH 019/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T10:24?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 4 ++--
.github/brain/repo-snapshot.md | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index 82975a73..bf77a9b1 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T06:52:39.931Z",
+ "generated_at": "2026-03-13T10:24:44.227Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
@@ -14,7 +14,7 @@
"hli_implemented": 7,
"hli_coverage_pct": "33%",
"last_ci_run": "2026-03-05T16:07:24.070Z",
- "memory_last_updated": "2026-03-13T03:31:06.282Z"
+ "memory_last_updated": "2026-03-13T08:53:32.764Z"
},
"zones": [
{
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index d67e857d..9083ca73 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 14:52 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 18:24 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 39 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 14:52 CST |
+| 快照生成时间 | 2026-03-13 18:24 CST |
---
@@ -266,9 +266,9 @@
## 🕐 最近动态(memory.json 最新3条)
+- `2026-03-13T08:53:32.764Z` · daily_check — passed
- `2026-03-12T08:55:54.205Z` · daily_check — passed
- `2026-03-11T08:55:43.347Z` · daily_check — passed
-- `2026-03-10T08:56:23.978Z` · daily_check — passed
---
From 15f96525e34c72ee8619952aafbb5a368fffc0d4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 10:26:14 +0000
Subject: [PATCH 020/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 42 ++++++++++++++++++++----------------------
1 file changed, 20 insertions(+), 22 deletions(-)
diff --git a/README.md b/README.md
index 6e1b368c..c4ac5fae 100644
--- a/README.md
+++ b/README.md
@@ -92,31 +92,29 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 14:53 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
-| 03-13 14:52 | ❌ 📢 更新系统公告区 · 失败 | 冰朔 |
-| 03-13 14:52 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
-| 03-13 14:52 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
-| 03-13 14:52 | ⏹️ 📢 更新系统公告区 · cancelled | WENZHUOXI |
-| 03-13 14:51 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | WENZHUOXI |
-| 03-13 14:50 | ✅ 📢 更新系统公告区 · 成功 | WENZHUOXI |
-| 03-13 14:50 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | WENZHUOXI |
-| 03-13 14:37 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
-| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
+| 03-13 18:25 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | zhizhi200271 |
+| 03-13 18:25 | ⏹️ 📢 更新系统公告区 · cancelled | zhizhi200271 |
+| 03-13 18:24 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
+| 03-13 18:24 | ⏹️ 📢 更新系统公告区 · cancelled | zhizhi200271 |
+| 03-13 18:24 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | zhizhi200271 |
+| 03-13 18:24 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
+| 03-13 18:06 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 17:49 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 17:39 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 16:56 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 16:53 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 16:53 | ✅ 铸渊 Brain Sync · 成功 | 冰朔 |
+| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-13 16:06 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 15:56 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
### 🤖 铸渊自动提醒
-> 🔴 **需要冰朔手动干预!**
+> 🟢 **今日无需冰朔手动干预** · 系统一切正常
>
-> - ❌ 📢 更新系统公告区 · 失败
->
-> 🗓️ 2026-03-13 · 铸渊已发送邮件提醒
+> 🗓️ 2026-03-13 · 铸渊自动检测
---
@@ -130,9 +128,9 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 14:52 | 冰朔 | `—/` | ✅ 上传成功 |
-| 03-13 14:52 | Copilot | `—/` | ✅ 上传成功 |
-| 03-13 14:50 | WENZHUOXI | `—/` | ✅ 上传成功 |
+| 03-13 18:24 | zhizhi200271 | `—/` | ✅ 上传成功 |
+| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
+| 03-13 15:15 | Copilot | `—/` | 🔵 已更新 |
### 🤖 铸渊自动提醒 · 合作者
From ad137583160f21052d959396843ffdd29f0970b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A3=9E=E6=AF=9B=E5=98=B4=E5=B8=85=E6=B0=94=E7=88=B1?=
Date: Fri, 13 Mar 2026 20:32:27 +0800
Subject: [PATCH 021/116] 20260313: Add feishu webhook config log
---
20260313_feishu_webhook_log.md | 56 ++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 20260313_feishu_webhook_log.md
diff --git a/20260313_feishu_webhook_log.md b/20260313_feishu_webhook_log.md
new file mode 100644
index 00000000..1863350f
--- /dev/null
+++ b/20260313_feishu_webhook_log.md
@@ -0,0 +1,56 @@
+\# 2026-03-13 飞书WebHook配置完成日志
+
+\## 一、任务目标
+
+配置飞书WebHook链路,实现飞书消息事件推送至阿里云服务器的Node.js服务
+
+
+
+\## 二、关键操作记录
+
+| 操作时间 | 操作内容 | 操作结果 |
+
+|----------|----------|----------|
+
+| 19:00-19:30 | 飞书开放平台配置WebHook地址:`https://guanghulab.com/webhook/feishu`,订阅`im.message.receive\_v1`消息事件 | 地址配置/事件订阅成功 |
+
+| 19:30-20:00 | 飞书客户端查找机器人(因索引未更新暂未找到) | 确认仅为展示问题,不影响功能 |
+
+| 20:00-20:20 | Windows终端curl验证WebHook连通性 | 返回`{"challenge":"test123"}`,基础链路通 |
+
+| 20:20-20:40 | 阿里云服务器替换`/var/www/hololake/server.js`,修复JSON解析Bug | 修复`Cannot read properties of undefined`错误 |
+
+| 20:40-20:50 | 重启Node.js服务 | 服务启动成功,日志:`🚀 服务启动成功,端口:3000` |
+
+| 20:50-21:00 | 二次curl验证 | 成功返回`{"challenge":"test123"}`,链路全通 |
+
+
+
+\## 三、环境信息
+
+1\. 服务器:阿里云ECS Linux(`root@iZf8z4nezg5bs9kl9eyth1Z`)
+
+2\. 服务:Node.js(3000端口)+ Nginx转发(`https://guanghulab.com/webhook/feishu`)
+
+3\. 依赖:express、body-parser
+
+4\. 日志路径:`/var/www/hololake/webhook.log`
+
+
+
+\## 四、任务结论
+
+✅ 飞书WebHook链路全通,可正常接收消息推送
+
+✅ 代码Bug已修复,服务稳定运行
+
+✅ 机器人未找到为索引延迟,不影响功能
+
+
+
+\## 五、后续建议
+
+1\. 查看消息日志:`tail -f /var/www/hololake/webhook.log`
+
+2\. 机器人聊天窗口1-2小时后可正常搜索
+
From 041b3571e106b1f8981fa9a4c9919e75ff72684b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 12:32:51 +0000
Subject: [PATCH 022/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index c4ac5fae..62877f48 100644
--- a/README.md
+++ b/README.md
@@ -92,21 +92,21 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 18:25 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | zhizhi200271 |
+| 03-13 20:32 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | feimaozuishuaiqi-ai |
+| 03-13 20:02 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 19:53 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 19:36 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 19:26 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 18:54 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 18:46 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 18:26 | ✅ 📢 更新系统公告区 · 成功 | zhizhi200271 |
| 03-13 18:25 | ⏹️ 📢 更新系统公告区 · cancelled | zhizhi200271 |
| 03-13 18:24 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
-| 03-13 18:24 | ⏹️ 📢 更新系统公告区 · cancelled | zhizhi200271 |
-| 03-13 18:24 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | zhizhi200271 |
| 03-13 18:24 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
-| 03-13 18:06 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 17:49 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 17:39 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 16:56 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 16:53 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 16:53 | ✅ 铸渊 Brain Sync · 成功 | 冰朔 |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-13 16:06 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 15:56 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 14:37 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
+| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
### 🤖 铸渊自动提醒
@@ -128,9 +128,10 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 18:24 | zhizhi200271 | `—/` | ✅ 上传成功 |
+| 03-13 20:32 | feimaozuishuaiqi-ai | `—/` | ✅ 上传成功 |
+| 03-13 19:17 | Copilot | `—/` | ✅ 上传成功 |
+| 03-13 19:02 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
-| 03-13 15:15 | Copilot | `—/` | 🔵 已更新 |
### 🤖 铸渊自动提醒 · 合作者
From b31a00a9e178725ed0b41edd6ec2b84617e56b5f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 12:32:51 +0000
Subject: [PATCH 023/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T12:32?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 2 +-
.github/brain/repo-snapshot.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index bf77a9b1..f971041b 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T10:24:44.227Z",
+ "generated_at": "2026-03-13T12:32:51.331Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 9083ca73..119e9f4e 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 18:24 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 20:32 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 39 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 18:24 CST |
+| 快照生成时间 | 2026-03-13 20:32 CST |
---
From 2aa65525b0f643513a79ec10a4f6a574c09a1db2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 12:34:23 +0000
Subject: [PATCH 024/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 62877f48..1218a79f 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,10 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 20:34 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | feimaozuishuaiqi-ai |
+| 03-13 20:33 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 20:32 | ✅ 📢 更新系统公告区 · 成功 | feimaozuishuaiqi-ai |
+| 03-13 20:32 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 20:32 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | feimaozuishuaiqi-ai |
| 03-13 20:02 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
| 03-13 19:53 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
@@ -99,14 +103,10 @@
| 03-13 19:26 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
| 03-13 18:54 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
| 03-13 18:46 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 18:26 | ✅ 📢 更新系统公告区 · 成功 | zhizhi200271 |
-| 03-13 18:25 | ⏹️ 📢 更新系统公告区 · cancelled | zhizhi200271 |
-| 03-13 18:24 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 18:24 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 14:37 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
### 🤖 铸渊自动提醒
From 541281d1f1adfda61650357847955a9f95c5166b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 12:45:39 +0000
Subject: [PATCH 025/116] Initial plan
From 8addc17ccc940d92d8b67b2f41e2e98c2f8ef8ee Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 12:52:28 +0000
Subject: [PATCH 026/116] fix: exclude SYSLOG issues from zhuyuan-issue-reply +
add title/body detection to syslog-issue-pipeline
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Problem: When developers submit SYSLOG via the 🚀 开发者入口, zhuyuan-issue-reply.yml
intercepts the issue and responds with "已记录。霜砚会在下次巡检时处理" instead of letting
syslog-issue-pipeline.yml handle the full auto-processing loop.
Root causes:
1. zhuyuan-issue-reply.yml triggers on ALL new issues with no exclusion for SYSLOG
2. syslog-issue-pipeline.yml only detects via 'syslog' label which may not exist in repo
3. zhuyuan-issue-reply.js has no guard against SYSLOG issues, falls through to generic fallback
Fixes:
- zhuyuan-issue-reply.yml: Exclude issues with syslog/bingshuo-deploy labels, SYSLOG/系统日志
in title, or '### 广播编号' in body (template field marker)
- syslog-issue-pipeline.yml: Detect SYSLOG issues by label OR title OR body pattern
- zhuyuan-issue-reply.js: Add isSyslog guard in handleIssueTrigger() as defense-in-depth
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/syslog-issue-pipeline.yml | 7 ++++++-
.github/workflows/zhuyuan-issue-reply.yml | 8 +++++++-
scripts/zhuyuan-issue-reply.js | 7 +++++++
3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index 27ac9cfc..996977e7 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -34,7 +34,12 @@ jobs:
process:
name: 📡 处理 SYSLOG 提交 / 广播提问 (Issue)
runs-on: ubuntu-latest
- if: contains(toJSON(github.event.issue.labels), 'syslog')
+ # 检测 SYSLOG 提交:通过标签、标题或正文模板字段识别
+ if: >
+ contains(toJSON(github.event.issue.labels), 'syslog') ||
+ contains(github.event.issue.title, 'SYSLOG') ||
+ contains(github.event.issue.title, '系统日志') ||
+ contains(github.event.issue.body, '### 广播编号')
permissions:
contents: write
issues: write
diff --git a/.github/workflows/zhuyuan-issue-reply.yml b/.github/workflows/zhuyuan-issue-reply.yml
index 6b713545..218393aa 100644
--- a/.github/workflows/zhuyuan-issue-reply.yml
+++ b/.github/workflows/zhuyuan-issue-reply.yml
@@ -11,8 +11,14 @@ jobs:
name: 🤖 铸渊回答问题
runs-on: ubuntu-latest
# 仅在 Issue 新建或评论中包含 @铸渊 / 铸渊 时触发
+ # 排除 SYSLOG 提交(由 syslog-issue-pipeline.yml 处理)和 bingshuo-deploy(由 bingshuo-deploy-agent.yml 处理)
if: >
- github.event_name == 'issues' ||
+ (github.event_name == 'issues' &&
+ !contains(join(github.event.issue.labels.*.name, ','), 'syslog') &&
+ !contains(join(github.event.issue.labels.*.name, ','), 'bingshuo-deploy') &&
+ !contains(github.event.issue.title, 'SYSLOG') &&
+ !contains(github.event.issue.title, '系统日志') &&
+ !contains(github.event.issue.body, '### 广播编号')) ||
(github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '铸渊'))
permissions:
diff --git a/scripts/zhuyuan-issue-reply.js b/scripts/zhuyuan-issue-reply.js
index 0841d074..68089494 100644
--- a/scripts/zhuyuan-issue-reply.js
+++ b/scripts/zhuyuan-issue-reply.js
@@ -103,6 +103,7 @@ function containsSystemCommand(text) {
}
// === 判断Issue类型 ===
+const isSyslog = issueLabels.includes('syslog') || /SYSLOG|系统日志/.test(issueTitle) || issueBody.includes('### 广播编号');
const isProgressQuery = issueLabels.includes('progress-query');
const isDevQuestion = issueLabels.includes('dev-question');
@@ -290,6 +291,12 @@ async function handleCollaboratorComment(user) {
async function handleIssueTrigger() {
let reply = '';
+ // --- SYSLOG 提交:由 syslog-issue-pipeline 处理,此处跳过 ---
+ if (isSyslog) {
+ console.log('📡 SYSLOG Issue detected, skipping (handled by syslog-issue-pipeline)');
+ return;
+ }
+
// --- 进度查询(指定开发者)---
if (isProgressQuery && devInfo) {
reply = `## ⚒️ 铸渊回复 · 进度查询\n\n`;
From 153a029419f7bae17da132744f1a7df8f3086fcc Mon Sep 17 00:00:00 2001
From: bingshuo-neural-system
Date: Fri, 13 Mar 2026 13:29:31 +0000
Subject: [PATCH 027/116] =?UTF-8?q?=F0=9F=A7=A0=20=E5=86=B0=E6=9C=94?=
=?UTF-8?q?=E4=B8=BB=E6=8E=A7=E7=A5=9E=E7=BB=8F=E7=B3=BB=E7=BB=9F=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E7=BC=96=E8=AF=91=202026-03-13T13:29:31Z?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/bingshuo-issues-index.json | 2 +-
.github/brain/bingshuo-master-brain.md | 4 ++--
.github/brain/bingshuo-system-health.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index eded71d9..24b7582e 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
- "updated_at": "2026-03-13T06:52:24.833Z",
+ "updated_at": "2026-03-13T13:29:31.637Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index 2f10a3cf..6434e0b5 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
-> 最后编译时间:2026-03-13T06:52:24.834Z
+> 最后编译时间:2026-03-13T13:29:31.637Z
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-13T06:52:24.834Z
+- **编译时间**:2026-03-13T13:29:31.637Z
- **脑文件规则版本**:v3.0
- **脑文件完整性**:✅ 完整
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index a1ee0628..a1f64fb6 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
- "updated_at": "2026-03-13T06:52:24.833Z",
+ "updated_at": "2026-03-13T13:29:31.636Z",
"health": {
"brain_consistency": {
"status": "yellow",
From fa9b63ae162fdcab3a8501de8d400140c427c137 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 13:29:47 +0000
Subject: [PATCH 028/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T13:29?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 2 +-
.github/brain/repo-snapshot.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index f971041b..fa81a5b2 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T12:32:51.331Z",
+ "generated_at": "2026-03-13T13:29:46.819Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 119e9f4e..ef91c19f 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 20:32 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 21:29 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 39 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 20:32 CST |
+| 快照生成时间 | 2026-03-13 21:29 CST |
---
From 185a9552a973cc4317652cff4b4bb002a134a992 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:29:48 +0000
Subject: [PATCH 029/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 1218a79f..b8850965 100644
--- a/README.md
+++ b/README.md
@@ -92,21 +92,21 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 20:34 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | feimaozuishuaiqi-ai |
+| 03-13 21:29 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
+| 03-13 20:52 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
+| 03-13 20:52 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
+| 03-13 20:34 | ✅ 📢 更新系统公告区 · 成功 | feimaozuishuaiqi-ai |
| 03-13 20:33 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
| 03-13 20:32 | ✅ 📢 更新系统公告区 · 成功 | feimaozuishuaiqi-ai |
-| 03-13 20:32 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 20:32 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | feimaozuishuaiqi-ai |
-| 03-13 20:02 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 19:53 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 19:36 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 19:26 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 18:54 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 18:46 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
| 03-13 18:24 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-13 14:37 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
### 🤖 铸渊自动提醒
@@ -128,9 +128,9 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
+| 03-13 21:29 | 冰朔 | `—/` | ✅ 上传成功 |
+| 03-13 21:29 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 20:32 | feimaozuishuaiqi-ai | `—/` | ✅ 上传成功 |
-| 03-13 19:17 | Copilot | `—/` | ✅ 上传成功 |
-| 03-13 19:02 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
From ce87244a841ce73f93aed32e5c7e874f298c1401 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:31:21 +0000
Subject: [PATCH 030/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index b8850965..7a788dc2 100644
--- a/README.md
+++ b/README.md
@@ -92,13 +92,13 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 21:30 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 21:29 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-13 21:29 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 21:29 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
-| 03-13 20:52 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
| 03-13 20:52 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 20:34 | ✅ 📢 更新系统公告区 · 成功 | feimaozuishuaiqi-ai |
| 03-13 20:33 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 20:32 | ✅ 📢 更新系统公告区 · 成功 | feimaozuishuaiqi-ai |
-| 03-13 20:32 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | feimaozuishuaiqi-ai |
| 03-13 18:24 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
@@ -130,7 +130,6 @@
|------|--------|------|------|
| 03-13 21:29 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-13 21:29 | Copilot | `—/` | ✅ 上传成功 |
-| 03-13 20:32 | feimaozuishuaiqi-ai | `—/` | ✅ 上传成功 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
From 57f5e2de450d63a9cda6e6bf7099b86d21e03452 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 13:38:04 +0000
Subject: [PATCH 031/116] =?UTF-8?q?=F0=9F=93=8B=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=85=89=E6=B9=96=E7=BA=AA=E5=85=83=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97=E6=96=87=E6=A1=A3=20=C2=B7=202026-03-13=2013:38=20UTC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/HoloLake-Era-OS-Modules.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/HoloLake-Era-OS-Modules.md b/docs/HoloLake-Era-OS-Modules.md
index 2895784a..06e1ea24 100644
--- a/docs/HoloLake-Era-OS-Modules.md
+++ b/docs/HoloLake-Era-OS-Modules.md
@@ -1,6 +1,6 @@
# HoloLake Era 操作系统部署模块
-> 📋 **自动生成文档** · 铸渊(ZhùYuān)维护 · 最后更新:2026-03-13 10:24 UTC
+> 📋 **自动生成文档** · 铸渊(ZhùYuān)维护 · 最后更新:2026-03-13 13:38 UTC
>
> 本文档由 GitHub Actions 自动触发生成,每当合作者上传/更新模块时自动刷新。
> 按合作者编号(DEV-XXX)整理所有已上传模块。
@@ -359,7 +359,7 @@ m11-module/
| 已上传模块数 | 12 |
| 待上传模块数 | 0 |
| 上传完成率 | 100% |
-| 文档更新时间 | 2026-03-13 10:24 UTC |
+| 文档更新时间 | 2026-03-13 13:38 UTC |
---
From 907ca112210c10b2afe34a89a651d549aa22304d Mon Sep 17 00:00:00 2001
From: bingshuo-neural-system
Date: Fri, 13 Mar 2026 13:38:06 +0000
Subject: [PATCH 032/116] =?UTF-8?q?=F0=9F=A7=A0=20=E5=86=B0=E6=9C=94?=
=?UTF-8?q?=E4=B8=BB=E6=8E=A7=E7=A5=9E=E7=BB=8F=E7=B3=BB=E7=BB=9F=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E7=BC=96=E8=AF=91=202026-03-13T13:38:06Z?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/bingshuo-issues-index.json | 2 +-
.github/brain/bingshuo-master-brain.md | 4 ++--
.github/brain/bingshuo-system-health.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index 24b7582e..40e08849 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
- "updated_at": "2026-03-13T13:29:31.637Z",
+ "updated_at": "2026-03-13T13:38:05.924Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index 6434e0b5..442b3166 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
-> 最后编译时间:2026-03-13T13:29:31.637Z
+> 最后编译时间:2026-03-13T13:38:05.924Z
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-13T13:29:31.637Z
+- **编译时间**:2026-03-13T13:38:05.924Z
- **脑文件规则版本**:v3.0
- **脑文件完整性**:✅ 完整
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index a1f64fb6..5db339fe 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
- "updated_at": "2026-03-13T13:29:31.636Z",
+ "updated_at": "2026-03-13T13:38:05.923Z",
"health": {
"brain_consistency": {
"status": "yellow",
From 665e86b80948afc11479a92e43acdae10ce17ce9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 13:38:20 +0000
Subject: [PATCH 033/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T13:38?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 2 +-
.github/brain/repo-snapshot.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index fa81a5b2..a27ec723 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T13:29:46.819Z",
+ "generated_at": "2026-03-13T13:38:20.317Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index ef91c19f..aa2f965c 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 21:29 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 21:38 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 39 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 21:29 CST |
+| 快照生成时间 | 2026-03-13 21:38 CST |
---
From 8fa2ce89ccb8822f59736046e4614d2e059004f8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:38:40 +0000
Subject: [PATCH 034/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 7a788dc2..d08a63a5 100644
--- a/README.md
+++ b/README.md
@@ -92,21 +92,21 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 21:38 | ⏹️ 📢 更新系统公告区 · cancelled | 冰朔 |
+| 03-13 21:38 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
+| 03-13 21:38 | 🔧 系统更新: `.github/` | bingshuo-neural-system |
+| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
+| 03-13 21:36 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 21:31 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 21:30 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
| 03-13 21:29 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 21:29 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 21:29 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
| 03-13 20:52 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
-| 03-13 20:34 | ✅ 📢 更新系统公告区 · 成功 | feimaozuishuaiqi-ai |
-| 03-13 20:33 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 18:24 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
### 🤖 铸渊自动提醒
@@ -128,9 +128,10 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
+| 03-13 21:38 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-13 21:29 | 冰朔 | `—/` | ✅ 上传成功 |
-| 03-13 21:29 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
+| 03-13 15:09 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
### 🤖 铸渊自动提醒 · 合作者
From 9bb310145db354a46bab0451081b617f3f7f9d0e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:39:43 +0000
Subject: [PATCH 035/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index d08a63a5..a8b1b3d3 100644
--- a/README.md
+++ b/README.md
@@ -92,13 +92,14 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 21:39 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 21:38 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-13 21:38 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 21:38 | ⏹️ 📢 更新系统公告区 · cancelled | 冰朔 |
| 03-13 21:38 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
-| 03-13 21:38 | 🔧 系统更新: `.github/` | bingshuo-neural-system |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 21:36 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
| 03-13 21:31 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 21:30 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
| 03-13 21:29 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 21:29 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
| 03-13 20:52 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
@@ -106,7 +107,6 @@
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
### 🤖 铸渊自动提醒
From fb66f55b4724b0b786ca79bdec14279fe9acaa87 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:44:01 +0000
Subject: [PATCH 036/116] Initial plan
From cecde1d924df7dde3673556f11a4c59b409b2870 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:53:55 +0000
Subject: [PATCH 037/116] feat: PM2 server diagnosis workflow + Notion report +
port conflict fix
- Add pm2-server-diagnose.yml workflow for SSH-based PM2 diagnosis
- Add server-diagnose-report.js script for Notion ticket creation
- Fix port conflict: guanghulab (src/index.js) now uses port 3001
- Update ecosystem.config.js to align with deploy workflow process names
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/deploy-to-server.yml | 9 +-
.github/workflows/pm2-server-diagnose.yml | 316 ++++++++++++++++++++++
ecosystem.config.js | 54 +++-
scripts/server-diagnose-report.js | 198 ++++++++++++++
4 files changed, 560 insertions(+), 17 deletions(-)
create mode 100644 .github/workflows/pm2-server-diagnose.yml
create mode 100644 scripts/server-diagnose-report.js
diff --git a/.github/workflows/deploy-to-server.yml b/.github/workflows/deploy-to-server.yml
index d9504e19..30f6fdf7 100644
--- a/.github/workflows/deploy-to-server.yml
+++ b/.github/workflows/deploy-to-server.yml
@@ -279,14 +279,15 @@ jobs:
echo '⚠️ WebSocket pm2 启动失败'
fi
- # 部署 src HLI 中间层
+ # 部署 src HLI 中间层(端口 3001,对应 Nginx /api/v1/ 路由)
+ # 注意:guanghulab-backend 已占用端口 3000,HLI 使用 3001 避免冲突
if [ -f '${{ secrets.DEPLOY_PATH }}/src/index.js' ]; then
echo '📦 安装 src 依赖...'
cd '${{ secrets.DEPLOY_PATH }}'
npm install --production 2>/dev/null || echo '⚠️ src npm install 失败'
- echo '🔄 重启 HLI 服务...'
- pm2 restart guanghulab 2>/dev/null || \
- pm2 start src/index.js --name guanghulab 2>/dev/null || \
+ echo '🔄 重启 HLI 服务(端口 3001)...'
+ pm2 delete guanghulab 2>/dev/null || true
+ PORT=3001 pm2 start src/index.js --name guanghulab --update-env 2>/dev/null || \
echo '⚠️ src pm2 启动失败'
fi
diff --git a/.github/workflows/pm2-server-diagnose.yml b/.github/workflows/pm2-server-diagnose.yml
new file mode 100644
index 00000000..642a594d
--- /dev/null
+++ b/.github/workflows/pm2-server-diagnose.yml
@@ -0,0 +1,316 @@
+name: "🔧 铸渊 · PM2 服务诊断与健康检查"
+
+# ━━━ PM2 服务诊断 + 全系统健康检查 + Notion 报告 ━━━
+# 手动触发:对服务器 PM2 进程进行全面排查,
+# 自动处理 errored 进程,并将诊断报告写入 Notion 工单队列。
+#
+# 必需 GitHub Secrets:
+# DEPLOY_HOST — 服务器 IP
+# DEPLOY_USER — SSH 用户名
+# DEPLOY_KEY — SSH 私钥(完整 PEM 内容)
+# DEPLOY_PATH — 网站根目录(如 /var/www/guanghulab)
+#
+# 可选 Secrets(有则自动写 Notion 工单):
+# NOTION_API_TOKEN — Notion 集成 token
+# NOTION_TICKET_DB_ID — 工单队列数据库 ID(默认 84edd0640bf146a9a5a7840107013e8c)
+
+on:
+ workflow_dispatch:
+ inputs:
+ auto_cleanup:
+ description: '是否自动清理 errored 进程(删除启动路径不存在的进程)'
+ required: false
+ default: 'true'
+ type: choice
+ options:
+ - 'true'
+ - 'false'
+
+permissions:
+ contents: read
+
+jobs:
+ diagnose:
+ name: "🔍 PM2 诊断 + 系统健康检查"
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: 📥 检出代码
+ uses: actions/checkout@v4
+
+ - name: 🟢 配置 Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+
+ - name: 🔑 配置 SSH 连接
+ run: |
+ mkdir -p ~/.ssh
+ echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key
+ chmod 600 ~/.ssh/deploy_key
+ ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null || {
+ echo "⚠️ ssh-keyscan 失败,使用 StrictHostKeyChecking=no 后备"
+ }
+
+ # ━━━ 阶段一:PM2 服务诊断 ━━━
+ - name: "🔧 阶段一 · PM2 errored 服务排查"
+ id: pm2_diagnose
+ run: |
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo " 阶段一 · PM2 errored 服务排查"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+ PM2_REPORT=$(ssh -i ~/.ssh/deploy_key \
+ -o StrictHostKeyChecking=no \
+ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
+ echo "══════════════════════════════════════"
+ echo " PM2 进程状态概览"
+ echo "══════════════════════════════════════"
+ pm2 status 2>/dev/null || echo "❌ pm2 未安装或不可用"
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " errored 进程详细日志"
+ echo "══════════════════════════════════════"
+
+ # 遍历所有 errored 进程
+ ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c "
+ import sys, json
+ try:
+ procs = json.load(sys.stdin)
+ for p in procs:
+ if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\":
+ name = p.get(\"name\", \"unknown\")
+ pm_id = p.get(\"pm_id\", \"?\")
+ script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\")
+ restarts = p.get(\"pm2_env\", {}).get(\"restart_time\", 0)
+ print(f\"{pm_id}|{name}|{script}|{restarts}\")
+ except:
+ pass
+ " 2>/dev/null)
+
+ if [ -z "$ERRORED_LIST" ]; then
+ echo "✅ 没有 errored 进程"
+ else
+ echo "$ERRORED_LIST" | while IFS="|" read -r pm_id name script restarts; do
+ echo ""
+ echo "━━━ 进程: $name (PM2 ID: $pm_id) ━━━"
+ echo " 启动脚本: $script"
+ echo " 重启次数: $restarts"
+
+ # 检查脚本文件是否存在
+ if [ -f "$script" ]; then
+ echo " 脚本文件: ✅ 存在"
+ else
+ echo " 脚本文件: ❌ 不存在"
+ fi
+
+ # 查看最近日志
+ echo " 最近错误日志 (最后 20 行):"
+ pm2 logs "$name" --lines 20 --nostream --err 2>/dev/null || echo " (无法读取日志)"
+ echo ""
+ done
+ fi
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " 所有进程详细信息"
+ echo "══════════════════════════════════════"
+ pm2 jlist 2>/dev/null | python3 -c "
+ import sys, json
+ try:
+ procs = json.load(sys.stdin)
+ for p in procs:
+ env = p.get(\"pm2_env\", {})
+ print(f\" [{p.get(\"pm_id\")}] {p.get(\"name\")} | status={env.get(\"status\")} | script={env.get(\"pm_exec_path\",\"?\")} | restarts={env.get(\"restart_time\",0)} | memory={p.get(\"monit\",{}).get(\"memory\",0)}\")
+ except Exception as e:
+ print(f\" 解析失败: {e}\")
+ " 2>/dev/null
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " 端口占用情况"
+ echo "══════════════════════════════════════"
+ netstat -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \
+ ss -tlnp 2>/dev/null | grep -E "3000|3001|3002|3721|8080" || \
+ echo " (netstat/ss 不可用)"
+ ')
+
+ echo "$PM2_REPORT"
+
+ # 保存到文件供后续步骤使用
+ echo "$PM2_REPORT" > /tmp/pm2-report.txt
+
+ # 设置输出
+ {
+ echo "pm2_report<> "$GITHUB_OUTPUT"
+
+ # ━━━ 阶段一 · 自动清理 ━━━
+ - name: "🧹 阶段一 · 自动清理 errored 进程"
+ if: ${{ inputs.auto_cleanup == 'true' }}
+ id: pm2_cleanup
+ run: |
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo " 自动清理 errored 进程"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+ CLEANUP_REPORT=$(ssh -i ~/.ssh/deploy_key \
+ -o StrictHostKeyChecking=no \
+ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
+ ERRORED_LIST=$(pm2 jlist 2>/dev/null | python3 -c "
+ import sys, json
+ try:
+ procs = json.load(sys.stdin)
+ for p in procs:
+ if p.get(\"pm2_env\", {}).get(\"status\") == \"errored\":
+ name = p.get(\"name\", \"unknown\")
+ script = p.get(\"pm2_env\", {}).get(\"pm_exec_path\", \"unknown\")
+ print(f\"{name}|{script}\")
+ except:
+ pass
+ " 2>/dev/null)
+
+ CLEANED=""
+ SKIPPED=""
+
+ if [ -z "$ERRORED_LIST" ]; then
+ echo "✅ 没有需要清理的 errored 进程"
+ else
+ echo "$ERRORED_LIST" | while IFS="|" read -r name script; do
+ if [ ! -f "$script" ]; then
+ echo "🗑️ 删除 $name(脚本 $script 不存在)"
+ pm2 delete "$name" 2>/dev/null && echo " ✅ 已删除" || echo " ❌ 删除失败"
+ else
+ echo "⚠️ 保留 $name(脚本 $script 存在,可能是运行时错误)"
+ echo " 尝试重启..."
+ pm2 restart "$name" 2>/dev/null && echo " ✅ 重启成功" || echo " ❌ 重启失败"
+ fi
+ done
+
+ echo ""
+ echo "📊 清理后 PM2 状态:"
+ pm2 status 2>/dev/null
+ pm2 save 2>/dev/null || echo "⚠️ pm2 save 失败"
+ fi
+ ')
+
+ echo "$CLEANUP_REPORT"
+ echo "$CLEANUP_REPORT" > /tmp/cleanup-report.txt
+
+ {
+ echo "cleanup_report<> "$GITHUB_OUTPUT"
+
+ # ━━━ 阶段二:全系统健康检查 ━━━
+ - name: "🏥 阶段二 · 全系统健康检查"
+ id: health_check
+ run: |
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo " 阶段二 · 全系统健康检查"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+ HEALTH_REPORT=$(ssh -i ~/.ssh/deploy_key \
+ -o StrictHostKeyChecking=no \
+ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
+ echo "══════════════════════════════════════"
+ echo " Nginx 状态"
+ echo "══════════════════════════════════════"
+ nginx -t 2>&1 || echo "❌ Nginx 配置检测失败"
+ systemctl status nginx --no-pager -l 2>/dev/null | head -15 || echo "⚠️ systemctl 不可用"
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " SSL 证书有效期"
+ echo "══════════════════════════════════════"
+ if [ -f "/etc/letsencrypt/live/guanghulab.com/fullchain.pem" ]; then
+ openssl x509 -in /etc/letsencrypt/live/guanghulab.com/fullchain.pem -noout -dates 2>/dev/null || echo "⚠️ 无法读取 SSL 证书"
+ else
+ echo "⚠️ SSL 证书文件不存在于默认路径"
+ # 尝试查找其他证书路径
+ find /etc/letsencrypt/live/ -name "fullchain.pem" 2>/dev/null | head -3 || echo " 未找到 Let'\''s Encrypt 证书"
+ find /etc/nginx/ -name "*.pem" -o -name "*.crt" 2>/dev/null | head -3 || echo " 未找到 Nginx SSL 文件"
+ fi
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " 磁盘空间"
+ echo "══════════════════════════════════════"
+ df -h 2>/dev/null || echo "❌ df 命令不可用"
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " 内存使用"
+ echo "══════════════════════════════════════"
+ free -h 2>/dev/null || echo "❌ free 命令不可用"
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " PM2 最终状态"
+ echo "══════════════════════════════════════"
+ pm2 status 2>/dev/null || echo "❌ pm2 不可用"
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " Node.js 版本"
+ echo "══════════════════════════════════════"
+ node -v 2>/dev/null || echo "❌ Node.js 未安装"
+ npm -v 2>/dev/null || echo "❌ npm 未安装"
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " 网站可访问性检查"
+ echo "══════════════════════════════════════"
+ curl -sI https://guanghulab.com 2>/dev/null | head -5 || echo "⚠️ HTTPS 访问失败"
+ echo "---"
+ curl -s http://127.0.0.1:3000/health 2>/dev/null | head -c 200 || echo "⚠️ 端口 3000 无响应"
+ echo ""
+ echo "---"
+ curl -s http://127.0.0.1:3001/api/v1/ 2>/dev/null | head -c 200 || echo "⚠️ 端口 3001 无响应"
+ echo ""
+ echo "---"
+ curl -s http://127.0.0.1:3002/api/ps/ 2>/dev/null | head -c 200 || echo "⚠️ 端口 3002 无响应"
+ echo ""
+
+ echo ""
+ echo "══════════════════════════════════════"
+ echo " 系统运行时间"
+ echo "══════════════════════════════════════"
+ uptime 2>/dev/null || echo "❌ uptime 不可用"
+ ')
+
+ echo "$HEALTH_REPORT"
+ echo "$HEALTH_REPORT" > /tmp/health-report.txt
+
+ {
+ echo "health_report<> "$GITHUB_OUTPUT"
+
+ # ━━━ 阶段三:生成 Notion 诊断报告 ━━━
+ - name: "📋 阶段三 · 写入 Notion 工单"
+ if: ${{ secrets.NOTION_API_TOKEN != '' }}
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID || '84edd0640bf146a9a5a7840107013e8c' }}
+ PM2_REPORT: ${{ steps.pm2_diagnose.outputs.pm2_report }}
+ CLEANUP_REPORT: ${{ steps.pm2_cleanup.outputs.cleanup_report }}
+ HEALTH_REPORT: ${{ steps.health_check.outputs.health_report }}
+ run: node scripts/server-diagnose-report.js
+
+ - name: "📋 阶段三 · 写入 Notion 工单(环境检查)"
+ if: ${{ secrets.NOTION_API_TOKEN == '' }}
+ run: |
+ echo "⚠️ Notion credentials 未配置,跳过工单创建"
+ echo "诊断报告仅输出到 Actions 日志"
+
+ - name: "✅ 诊断完成"
+ run: |
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo " ✅ PM2 诊断完毕 · $(date '+%Y-%m-%d %H:%M:%S')"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
diff --git a/ecosystem.config.js b/ecosystem.config.js
index e1b55c1d..a5359737 100644
--- a/ecosystem.config.js
+++ b/ecosystem.config.js
@@ -1,32 +1,32 @@
// ecosystem.config.js
// PM2 进程管理配置
+//
+// 注意:进程名必须与 deploy-to-server.yml 中的 --name 参数保持一致。
+// 当前活跃服务(2026-03 更新):
+// guanghulab → HLI 中间层 → 端口 3001(/api/v1/)
+// guanghulab-proxy → AI Chat API 代理 → 端口 3721
+// guanghulab-backend → Express 后端 API → 端口 3000(/webhook/feishu)
+// guanghulab-ws → Status Board WS → 端口 8080
+// persona-studio → Persona Studio API → 端口 3002(/api/ps/)
module.exports = {
apps: [
{
name: 'guanghulab',
script: 'src/index.js',
- instances: 'max',
- exec_mode: 'cluster',
+ instances: 1,
+ exec_mode: 'fork',
watch: false,
env: {
NODE_ENV: 'production',
- PORT: 3000,
- },
- env_development: {
- NODE_ENV: 'development',
- PORT: 3000,
- },
- env_test: {
- NODE_ENV: 'test',
PORT: 3001,
},
log_date_format: 'YYYY-MM-DD HH:mm:ss',
- error_file: 'logs/error.log',
- out_file: 'logs/out.log',
+ error_file: 'logs/hli-error.log',
+ out_file: 'logs/hli-out.log',
},
{
- name: 'api-proxy',
+ name: 'guanghulab-proxy',
script: 'backend-integration/api-proxy.js',
instances: 1,
exec_mode: 'fork',
@@ -39,6 +39,34 @@ module.exports = {
error_file: 'logs/api-proxy-error.log',
out_file: 'logs/api-proxy-out.log',
},
+ {
+ name: 'guanghulab-backend',
+ script: 'backend/server.js',
+ instances: 1,
+ exec_mode: 'fork',
+ watch: false,
+ env: {
+ NODE_ENV: 'production',
+ PORT: 3000,
+ },
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
+ error_file: 'logs/backend-error.log',
+ out_file: 'logs/backend-out.log',
+ },
+ {
+ name: 'guanghulab-ws',
+ script: 'status-board/mock-ws-server.js',
+ instances: 1,
+ exec_mode: 'fork',
+ watch: false,
+ env: {
+ NODE_ENV: 'production',
+ PORT: 8080,
+ },
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
+ error_file: 'logs/ws-error.log',
+ out_file: 'logs/ws-out.log',
+ },
{
name: 'persona-studio',
script: 'persona-studio/backend/server.js',
diff --git a/scripts/server-diagnose-report.js b/scripts/server-diagnose-report.js
new file mode 100644
index 00000000..aac33c46
--- /dev/null
+++ b/scripts/server-diagnose-report.js
@@ -0,0 +1,198 @@
+// scripts/server-diagnose-report.js
+// 铸渊 · PM2 服务诊断报告 → Notion 工单
+//
+// 将 PM2 诊断和系统健康检查结果写入 Notion 工单队列。
+// 由 pm2-server-diagnose.yml 工作流调用。
+//
+// 环境变量:
+// NOTION_TOKEN Notion API token
+// NOTION_TICKET_DB_ID 工单队列数据库 ID
+// PM2_REPORT PM2 诊断输出
+// CLEANUP_REPORT 清理操作输出(可选)
+// HEALTH_REPORT 健康检查输出
+
+'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 PM2_REPORT = process.env.PM2_REPORT || '';
+const CLEANUP_REPORT = process.env.CLEANUP_REPORT || '';
+const HEALTH_REPORT = process.env.HEALTH_REPORT || '';
+
+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;
+}
+
+// ══════════════════════════════════════════════════════════
+// 构建报告内容
+// ══════════════════════════════════════════════════════════
+
+function buildReportContent() {
+ var now = new Date().toISOString();
+ var dateStr = now.slice(0, 10);
+
+ var sections = [];
+
+ sections.push('## 📋 PM2 服务排查报告 · ' + dateStr);
+ sections.push('');
+ sections.push('| 字段 | 值 |');
+ sections.push('|------|-----|');
+ sections.push('| 执行者 | 铸渊Agent |');
+ sections.push('| 执行时间 | ' + now + ' |');
+ sections.push('| 来源 | GitHub Actions · pm2-server-diagnose |');
+ sections.push('');
+
+ if (PM2_REPORT) {
+ sections.push('## 阶段一 · PM2 诊断结果');
+ sections.push('');
+ sections.push(PM2_REPORT.slice(0, 3000));
+ sections.push('');
+ }
+
+ if (CLEANUP_REPORT) {
+ sections.push('## 清理操作结果');
+ sections.push('');
+ sections.push(CLEANUP_REPORT.slice(0, 2000));
+ sections.push('');
+ }
+
+ if (HEALTH_REPORT) {
+ sections.push('## 阶段二 · 全系统健康检查');
+ sections.push('');
+ sections.push(HEALTH_REPORT.slice(0, 3000));
+ sections.push('');
+ }
+
+ return sections.join('\n');
+}
+
+// ══════════════════════════════════════════════════════════
+// 主函数 — 创建 Notion 诊断工单
+// ══════════════════════════════════════════════════════════
+
+async function createDiagnoseTicket() {
+ if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) {
+ console.log('⚠️ Notion credentials not configured, skipping ticket creation');
+ console.log(' NOTION_TOKEN: ' + (NOTION_TOKEN ? '✅' : '❌'));
+ console.log(' NOTION_TICKET_DB_ID: ' + (NOTION_TICKET_DB_ID ? '✅' : '❌'));
+ process.exit(0);
+ }
+
+ var now = new Date();
+ var dateStr = now.toISOString().slice(0, 10);
+ var title = 'PM2服务排查报告 · ' + dateStr;
+ var contentText = buildReportContent();
+
+ // 将内容拆分成多个段落块(Notion 单个 rich_text 限制 2000 字符)
+ var contentBlocks = [];
+ var remaining = contentText;
+
+ while (remaining.length > 0) {
+ var chunk = remaining.slice(0, NOTION_RICH_TEXT_MAX);
+ remaining = remaining.slice(NOTION_RICH_TEXT_MAX);
+ contentBlocks.push({
+ object: 'block',
+ type: 'paragraph',
+ paragraph: {
+ rich_text: [{ type: 'text', text: { content: chunk } }],
+ },
+ });
+ }
+
+ // 确保至少有一个内容块
+ if (contentBlocks.length === 0) {
+ contentBlocks.push({
+ object: 'block',
+ type: 'paragraph',
+ paragraph: {
+ rich_text: [{ type: 'text', text: { content: '诊断报告(无详细输出)' } }],
+ },
+ });
+ }
+
+ var body = {
+ parent: { database_id: NOTION_TICKET_DB_ID },
+ properties: {
+ '标题': { title: [{ type: 'text', text: { content: title } }] },
+ '操作类型': { select: { name: '其他' } },
+ '提交者': { rich_text: [{ type: 'text', text: { content: '铸渊Agent' } }] },
+ '状态': { select: { name: '已完成' } },
+ '优先级': { select: { name: 'P1' } },
+ },
+ children: contentBlocks,
+ };
+
+ console.log('📋 创建 PM2 诊断工单...');
+ console.log(' 标题: ' + title);
+ console.log(' 数据库: ' + NOTION_TICKET_DB_ID);
+ console.log(' 内容长度: ' + contentText.length + ' 字符');
+ console.log(' 内容块数: ' + contentBlocks.length);
+
+ try {
+ var result = await notionPost('/v1/pages', body);
+ console.log('✅ Notion 诊断工单已创建: ' + result.id);
+ console.log(' URL: ' + (result.url || 'N/A'));
+ } catch (err) {
+ console.error('❌ 工单创建失败: ' + err.message);
+ // 不以失败退出,诊断本身已成功
+ console.log('⚠️ 诊断结果已输出到 Actions 日志,Notion 写入失败不影响诊断');
+ process.exit(0);
+ }
+}
+
+createDiagnoseTicket();
From 983fd6e951c559c8a3456410277d540f1e1b3410 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 13:55:14 +0000
Subject: [PATCH 038/116] refactor: extract magic numbers as named constants in
diagnose report
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
scripts/server-diagnose-report.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/scripts/server-diagnose-report.js b/scripts/server-diagnose-report.js
index aac33c46..abe380ab 100644
--- a/scripts/server-diagnose-report.js
+++ b/scripts/server-diagnose-report.js
@@ -24,6 +24,9 @@ const HEALTH_REPORT = process.env.HEALTH_REPORT || '';
const NOTION_VERSION = '2022-06-28';
const NOTION_API_HOSTNAME = 'api.notion.com';
const NOTION_RICH_TEXT_MAX = 2000;
+const MAX_PM2_REPORT_LENGTH = 3000;
+const MAX_CLEANUP_REPORT_LENGTH = 2000;
+const MAX_HEALTH_REPORT_LENGTH = 3000;
// ══════════════════════════════════════════════════════════
// Notion API 工具
@@ -100,21 +103,21 @@ function buildReportContent() {
if (PM2_REPORT) {
sections.push('## 阶段一 · PM2 诊断结果');
sections.push('');
- sections.push(PM2_REPORT.slice(0, 3000));
+ sections.push(PM2_REPORT.slice(0, MAX_PM2_REPORT_LENGTH));
sections.push('');
}
if (CLEANUP_REPORT) {
sections.push('## 清理操作结果');
sections.push('');
- sections.push(CLEANUP_REPORT.slice(0, 2000));
+ sections.push(CLEANUP_REPORT.slice(0, MAX_CLEANUP_REPORT_LENGTH));
sections.push('');
}
if (HEALTH_REPORT) {
sections.push('## 阶段二 · 全系统健康检查');
sections.push('');
- sections.push(HEALTH_REPORT.slice(0, 3000));
+ sections.push(HEALTH_REPORT.slice(0, MAX_HEALTH_REPORT_LENGTH));
sections.push('');
}
From 8c4bbb3b02aa6920e9dd299bc1f3fc8c08f13f01 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:01:34 +0000
Subject: [PATCH 039/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index a8b1b3d3..5b849f37 100644
--- a/README.md
+++ b/README.md
@@ -92,21 +92,21 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 21:39 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 21:44 | 🔵 铸渊 · Bridge E · GitHub Changes → Notion · action_required | Copilot |
+| 03-13 21:43 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-13 21:39 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 21:38 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 21:38 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
-| 03-13 21:38 | ⏹️ 📢 更新系统公告区 · cancelled | 冰朔 |
-| 03-13 21:38 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
-| 03-13 21:36 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 21:31 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 21:29 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 21:29 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
| 03-13 20:52 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-12 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-11 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
+| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
### 🤖 铸渊自动提醒
@@ -128,8 +128,9 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 21:38 | 冰朔 | `—/` | ✅ 上传成功 |
-| 03-13 21:29 | 冰朔 | `—/` | ✅ 上传成功 |
+| 03-13 21:55 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 21:54 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 21:44 | Copilot | `—/` | 🔵 已更新 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
| 03-13 15:09 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
From 3d26ceaf24b0441a667fe98f6972b4c3d23f346c Mon Sep 17 00:00:00 2001
From: bingshuo-neural-system
Date: Fri, 13 Mar 2026 14:25:49 +0000
Subject: [PATCH 040/116] =?UTF-8?q?=F0=9F=A7=A0=20=E5=86=B0=E6=9C=94?=
=?UTF-8?q?=E4=B8=BB=E6=8E=A7=E7=A5=9E=E7=BB=8F=E7=B3=BB=E7=BB=9F=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E7=BC=96=E8=AF=91=202026-03-13T14:25:49Z?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/bingshuo-issues-index.json | 2 +-
.github/brain/bingshuo-master-brain.md | 8 ++++----
.github/brain/bingshuo-system-health.json | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index 40e08849..ba3c6ba5 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
- "updated_at": "2026-03-13T13:38:05.924Z",
+ "updated_at": "2026-03-13T14:25:49.756Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index 442b3166..a0f2c066 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
-> 最后编译时间:2026-03-13T13:38:05.924Z
+> 最后编译时间:2026-03-13T14:25:49.757Z
---
@@ -56,7 +56,7 @@
### 仓库统计
- 功能模块:10 个
-- Workflow:40 个
+- Workflow:41 个
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-13T13:38:05.924Z
+- **编译时间**:2026-03-13T14:25:49.757Z
- **脑文件规则版本**:v3.0
- **脑文件完整性**:✅ 完整
@@ -107,7 +107,7 @@
|--------|------|------|
| 🟡 brain_consistency | yellow | 主仓库脑文件完整,但与 persona-studio 脑文件的同步状态待验证 |
| 🟢 deployment_health | green | deploy-to-server.yml 与 deploy-pages.yml 均存在 |
-| 🟢 workflow_health | green | 40 个 workflow 已注册 |
+| 🟢 workflow_health | green | 41 个 workflow 已注册 |
| 🟡 routing_health | yellow | HLI 接口覆盖率 33.3%(7/21) |
| 🟢 docs_entry_health | green | docs/index.html 存在 |
| 🟡 persona_studio_health | yellow | 前后端结构存在,端到端对话链路待验证 |
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index 5db339fe..cec5ae04 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
- "updated_at": "2026-03-13T13:38:05.923Z",
+ "updated_at": "2026-03-13T14:25:49.756Z",
"health": {
"brain_consistency": {
"status": "yellow",
@@ -13,7 +13,7 @@
},
"workflow_health": {
"status": "green",
- "detail": "40 个 workflow 已注册"
+ "detail": "41 个 workflow 已注册"
},
"routing_health": {
"status": "yellow",
From 8f9fc635e7e3f1033db4285bda9bfd58bceb0785 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:26:03 +0000
Subject: [PATCH 041/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 5b849f37..edf5d6b6 100644
--- a/README.md
+++ b/README.md
@@ -92,13 +92,12 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 21:44 | 🔵 铸渊 · Bridge E · GitHub Changes → Notion · action_required | Copilot |
+| 03-13 22:01 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-13 21:55 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
+| 03-13 21:53 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
| 03-13 21:43 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
| 03-13 21:39 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 21:38 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 21:38 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
-| 03-13 20:52 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
| 03-12 16:55 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
@@ -107,6 +106,7 @@
| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
+| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
### 🤖 铸渊自动提醒
@@ -128,9 +128,8 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 21:55 | Copilot | `—/` | 🔵 已更新 |
-| 03-13 21:54 | Copilot | `—/` | 🔵 已更新 |
-| 03-13 21:44 | Copilot | `—/` | 🔵 已更新 |
+| 03-13 22:25 | 冰朔 | `—/` | ✅ 上传成功 |
+| 03-13 22:25 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
| 03-13 15:09 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
From 13a805cf8733325e4c3a092a050bc5b5fd5b1566 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 14:26:03 +0000
Subject: [PATCH 042/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T14:26?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 26 +++++++++++++++++++++-----
.github/brain/repo-snapshot.md | 14 ++++++++------
2 files changed, 29 insertions(+), 11 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index a27ec723..defeafa7 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,14 +1,14 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T13:38:20.317Z",
+ "generated_at": "2026-03-13T14:26:03.295Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
"zones": 13,
"total_modules": 10,
- "total_workflows": 40,
- "total_scripts": 39,
+ "total_workflows": 41,
+ "total_scripts": 40,
"total_dev_nodes": 8,
"hli_interfaces": 21,
"hli_implemented": 7,
@@ -303,6 +303,13 @@
"manual"
]
},
+ {
+ "file": "pm2-server-diagnose.yml",
+ "name": "\"🔧 铸渊 · PM2 服务诊断与健康检查\"",
+ "triggers": [
+ "manual"
+ ]
+ },
{
"file": "process-notion-orders.yml",
"name": "Process Notion Work Orders",
@@ -471,7 +478,7 @@
]
}
],
- "item_count": 40
+ "item_count": 41
},
{
"zone_id": "SCRIPTS",
@@ -572,6 +579,9 @@
{
"file": "send-feishu-alert.js"
},
+ {
+ "file": "server-diagnose-report.js"
+ },
{
"file": "sync-login-entry.js"
},
@@ -606,7 +616,7 @@
"file": "zhuyuan-module-protocol.js"
}
],
- "item_count": 39
+ "item_count": 40
},
{
"zone_id": "SRC",
@@ -1387,6 +1397,9 @@
"persona-invoke": [
"WORKFLOWS::persona-invoke.yml"
],
+ "pm2-server-diagnose": [
+ "WORKFLOWS::pm2-server-diagnose.yml"
+ ],
"process-notion-orders": [
"WORKFLOWS::process-notion-orders.yml"
],
@@ -1542,6 +1555,9 @@
"send-feishu-alert": [
"SCRIPTS::send-feishu-alert.js"
],
+ "server-diagnose-report": [
+ "SCRIPTS::server-diagnose-report.js"
+ ],
"update-brain": [
"SCRIPTS::update-brain.js"
],
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index aa2f965c..f5f40464 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 21:38 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 22:26 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -9,11 +9,11 @@
|------|------|
| 区域总数 | 13 个区域 |
| 功能模块 | 10 个 (m01~m18) |
-| 工作流 | 40 个 GitHub Actions |
-| 脚本 | 39 个执行脚本 |
+| 工作流 | 41 个 GitHub Actions |
+| 脚本 | 40 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 21:38 CST |
+| 快照生成时间 | 2026-03-13 22:26 CST |
---
@@ -30,12 +30,12 @@
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
### ⚡ 自动化工作流(WORKFLOWS)
-**路径**: `.github/workflows` · **数量**: 40 项
+**路径**: `.github/workflows` · **数量**: 41 项
**描述**: 所有 GitHub Actions 工作流定义
**关键词**: workflow · actions · ci · automation · 工作流 · 自动化
### 🔧 执行脚本库(SCRIPTS)
-**路径**: `scripts` · **数量**: 39 项
+**路径**: `scripts` · **数量**: 40 项
**描述**: 铸渊所有执行手脚 · 自动化脚本
**关键词**: script · node · js · 脚本 · 执行 · runner
@@ -108,6 +108,7 @@
| `notion-heartbeat.yml` | Notion Heartbeat Monitor | schedule(*/5 * * * *), manual |
| `notion-poll.yml` | 铸渊 · Notion 工单轮询 | schedule(*/15 * * * *), manual |
| `persona-invoke.yml` | Persona Invoke Endpoint | manual |
+| `pm2-server-diagnose.yml` | "🔧 铸渊 · PM2 服务诊断与健康检查" | manual |
| `process-notion-orders.yml` | Process Notion Work Orders | push, manual |
| `ps-on-build.yml` | "🌊 Persona Studio · 代码生成" | manual |
| `ps-on-chat.yml` | "🌊 Persona Studio · 对话处理" | manual |
@@ -163,6 +164,7 @@
- `scripts/save-collaboration-log.js`
- `scripts/selfcheck.js`
- `scripts/send-feishu-alert.js`
+- `scripts/server-diagnose-report.js`
- `scripts/sync-login-entry.js`
- `scripts/update-brain.js`
- `scripts/update-memory.js`
From 4110ac3ab351f4006ee8f6632ed75060adca43f5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:27:35 +0000
Subject: [PATCH 043/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index edf5d6b6..81715518 100644
--- a/README.md
+++ b/README.md
@@ -92,11 +92,12 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 22:27 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 22:26 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-13 22:26 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
+| 03-13 22:25 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
| 03-13 22:01 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 21:55 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
-| 03-13 21:53 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
-| 03-13 21:43 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
-| 03-13 21:39 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-13 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
@@ -106,7 +107,6 @@
| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
-| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
### 🤖 铸渊自动提醒
@@ -131,7 +131,6 @@
| 03-13 22:25 | 冰朔 | `—/` | ✅ 上传成功 |
| 03-13 22:25 | Copilot | `—/` | ✅ 上传成功 |
| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
-| 03-13 15:09 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
### 🤖 铸渊自动提醒 · 合作者
From 07275b4730ec617c5e63678f194d1602d4894c58 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:35:28 +0000
Subject: [PATCH 044/116] Initial plan
From dbfc96e4b8862a0080dad433c68ab480bd9a9171 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:46:19 +0000
Subject: [PATCH 045/116] fix: add SYSLOG immediate acknowledgment + progress
tracking for developer entry submissions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When a developer submits a SYSLOG via the 🚀 开发者入口, the system now:
1. Posts an immediate "📡 系统已收到" acknowledgment with full progress checklist (via zhuyuan-issue-reply.yml syslog-ack job)
2. Posts real-time progress updates as each pipeline stage completes (via syslog-issue-pipeline.yml)
3. Updates the progress comment to show all stages completed on success
4. Updates the progress comment to show error status on failure
Root cause: syslog-issue-pipeline.yml was never triggered by issues events (0 out of 36 runs were issue-triggered). The syslog-ack safety net in zhuyuan-issue-reply.yml ensures developers always get immediate feedback even if the main pipeline fails to trigger.
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/syslog-issue-pipeline.yml | 105 +++++++++++++++++++-
.github/workflows/zhuyuan-issue-reply.yml | 81 ++++++++++++++-
2 files changed, 180 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index 996977e7..e4e453f4 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -1,4 +1,4 @@
-name: SYSLOG Issue Pipeline
+name: 📡 SYSLOG Issue Pipeline
# 📡 SYSLOG 自助提交系统 · Issue 版全自动闭环
#
# 开发者在 GitHub Issue 提交 SYSLOG 或提问(使用 syslog-submit 模板)
@@ -56,6 +56,43 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts
+ - name: 📋 进度通报 · 管道已启动
+ id: progress
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = context.payload.issue.number;
+ const now = new Date().toISOString();
+
+ const body = [
+ '## ⚙️ SYSLOG 管道运行中',
+ '',
+ '> 管道启动时间: ' + now,
+ '',
+ '| 阶段 | 状态 | 说明 |',
+ '|------|------|------|',
+ '| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
+ '| ② 解析内容 | 🔄 进行中 | 正在解析广播编号、邮箱、内容 |',
+ '| ③ 模块验证 | ⏳ 等待中 | 检测模块上传状态 |',
+ '| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 |',
+ '| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单 |',
+ '| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |',
+ '| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到邮箱 |',
+ '| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |',
+ '',
+ '> *管道正在运行,请勿关闭此 Issue*'
+ ].join('\n');
+
+ const { data: comment } = await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: number,
+ body: body
+ });
+
+ core.setOutput('comment_id', comment.id);
+ console.log('📋 进度通报已发送, comment_id=' + comment.id);
+
- name: 🔍 Parse submission
id: parse
uses: actions/github-script@v7
@@ -243,6 +280,7 @@ jobs:
const email = '${{ steps.parse.outputs.email }}';
const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
+ const progressCommentId = '${{ steps.progress.outputs.comment_id }}';
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
const maskedEmail = email.length > 4
@@ -252,8 +290,37 @@ jobs:
? 'ℹ️ 未检测到模块引用'
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
+ // Update progress comment to show all completed
+ if (progressCommentId) {
+ try {
+ const progressBody = [
+ '## ⚙️ SYSLOG 管道运行完成 ✅',
+ '',
+ '| 阶段 | 状态 | 说明 |',
+ '|------|------|------|',
+ '| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
+ '| ② 解析内容 | ✅ 已完成 | 广播编号: `' + broadcastId + '` |',
+ '| ③ 模块验证 | ✅ 已完成 | ' + moduleStatus + ' |',
+ '| ④ 唤醒核心大脑 | ✅ 已完成 | 铸渊核心大脑已处理 |',
+ '| ⑤ 创建 Notion 工单 | ✅ 已完成 | 霜砚工单已推送 |',
+ '| ⑥ 生成广播 | ✅ 已完成 | 新广播已生成 |',
+ '| ⑦ 邮件通知 | ✅ 已完成 | 已发送至 `' + maskedEmail + '` |',
+ '| ⑧ 闭环确认 | ✅ 已完成 | 见下方最终结果 |',
+ ].join('\n');
+
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: Number(progressCommentId),
+ body: progressBody
+ });
+ } catch (e) {
+ console.log('⚠️ 更新进度评论失败: ' + e.message);
+ }
+ }
+
const body = [
- '✅ **已处理** · ' + typeLabel,
+ '## ✅ 已处理 · ' + typeLabel,
'',
'| 项目 | 内容 |',
'|------|------|',
@@ -266,7 +333,9 @@ jobs:
'> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
'> 结果已发送到你的邮箱,Notion 侧工单已创建。',
'>',
- '> 如未收到邮件,请检查垃圾箱或重新提交。'
+ '> 如未收到邮件,请检查垃圾箱或重新提交。',
+ '>',
+ '> *—— 铸渊(ICE-GL-ZY001)· 代码守护人格体*'
].join('\n');
await github.rest.issues.createComment({
@@ -295,12 +364,40 @@ jobs:
const number = context.payload.issue?.number;
if (!number) return;
+ const progressCommentId = '${{ steps.progress.outputs.comment_id }}';
+ const actionsUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions';
+
+ // Update progress comment to reflect failure
+ if (progressCommentId) {
+ try {
+ const progressBody = [
+ '## ⚙️ SYSLOG 管道运行异常 ❌',
+ '',
+ '| 阶段 | 状态 | 说明 |',
+ '|------|------|------|',
+ '| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
+ '| ② ~ ⑧ | ❌ 异常 | 管道处理过程中出错 |',
+ '',
+ '> 请检查提交格式或查看 [Actions 日志](' + actionsUrl + ')',
+ ].join('\n');
+
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: Number(progressCommentId),
+ body: progressBody
+ });
+ } catch (e) {
+ console.log('⚠️ 更新进度评论失败: ' + e.message);
+ }
+ }
+
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
- body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions)。'
+ body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊(ICE-GL-ZY001)*'
});
} catch (err) {
console.log('⚠️ 失败告警回复失败: ' + err.message);
diff --git a/.github/workflows/zhuyuan-issue-reply.yml b/.github/workflows/zhuyuan-issue-reply.yml
index 218393aa..3d5eff6e 100644
--- a/.github/workflows/zhuyuan-issue-reply.yml
+++ b/.github/workflows/zhuyuan-issue-reply.yml
@@ -7,11 +7,88 @@ on:
types: [created]
jobs:
+ # ════════════════════════════════════════════════════════════
+ # SYSLOG 即时确认 · 安全网
+ # 当开发者通过 Issue 提交 SYSLOG 时,立刻回复确认 + 进度清单
+ # 确保即使 syslog-issue-pipeline 未触发,开发者也能看到系统已收到
+ # ════════════════════════════════════════════════════════════
+ syslog-ack:
+ name: 📡 SYSLOG 提交确认
+ runs-on: ubuntu-latest
+ if: >
+ github.event_name == 'issues' &&
+ github.event.action == 'opened' &&
+ (contains(join(github.event.issue.labels.*.name, ','), 'syslog') ||
+ contains(github.event.issue.title, 'SYSLOG') ||
+ contains(github.event.issue.title, '系统日志') ||
+ contains(github.event.issue.body, '### 广播编号'))
+ permissions:
+ issues: write
+ steps:
+ - name: 📡 发送即时确认
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const body = context.payload.issue.body || '';
+ const number = context.payload.issue.number;
+ const author = context.payload.issue.user.login;
+
+ // Extract broadcast_id from body
+ const bcMatch = body.match(/###\s*广播编号\s*\n+([^\n#]+)/i);
+ const broadcastId = bcMatch ? bcMatch[1].trim() : '(待解析)';
+
+ // Extract type
+ const typeMatch = body.match(/###\s*类型\s*\n+([^\n#]+)/i);
+ const submitType = typeMatch ? typeMatch[1].trim() : 'SYSLOG';
+
+ const now = new Date().toISOString();
+
+ const comment = [
+ '## 📡 系统已收到 · SYSLOG 闭环处理启动',
+ '',
+ '> **提交者**: @' + author,
+ '> **广播编号**: `' + broadcastId + '`',
+ '> **类型**: ' + submitType,
+ '> **接收时间**: ' + now,
+ '',
+ '### 📋 处理进度',
+ '',
+ '| 阶段 | 状态 | 说明 |',
+ '|------|------|------|',
+ '| ① 接收提交 | ✅ 已完成 | 系统已收到你的 SYSLOG |',
+ '| ② 解析内容 | ⏳ 等待中 | 解析广播编号、邮箱、内容 |',
+ '| ③ 模块验证 | ⏳ 等待中 | 检测模块是否已上传到仓库 |',
+ '| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 SYSLOG |',
+ '| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单到 Notion |',
+ '| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |',
+ '| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到你的邮箱 |',
+ '| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |',
+ '',
+ '> 💡 SYSLOG 自动管道已启动,预计 2-5 分钟完成全部处理。',
+ '> 完成后本 Issue 下方会出现最终结果,你的邮箱也会收到通知。',
+ '>',
+ '> 如超过 10 分钟无后续回复,请在下方评论中 @铸渊 查询状态。',
+ '>',
+ '> *—— 铸渊(ICE-GL-ZY001)· 代码守护人格体*'
+ ].join('\n');
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: number,
+ body: comment
+ });
+
+ console.log('📡 SYSLOG 即时确认已发送 Issue #' + number);
+
+ # ════════════════════════════════════════════════════════════
+ # 开发者提问 · 铸渊自动回答
+ # 排除 SYSLOG 提交(由 syslog-issue-pipeline.yml 处理)
+ # 排除 bingshuo-deploy(由 bingshuo-deploy-agent.yml 处理)
+ # ════════════════════════════════════════════════════════════
auto-reply:
name: 🤖 铸渊回答问题
runs-on: ubuntu-latest
- # 仅在 Issue 新建或评论中包含 @铸渊 / 铸渊 时触发
- # 排除 SYSLOG 提交(由 syslog-issue-pipeline.yml 处理)和 bingshuo-deploy(由 bingshuo-deploy-agent.yml 处理)
if: >
(github.event_name == 'issues' &&
!contains(join(github.event.issue.labels.*.name, ','), 'syslog') &&
From 4787f07d22869e0b4dbc5e57e9172f796dd9bd54 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:47:31 +0000
Subject: [PATCH 046/116] fix: improve progressCommentId validation and error
message context
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/syslog-issue-pipeline.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index e4e453f4..abac3fd3 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -291,7 +291,7 @@ jobs:
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
// Update progress comment to show all completed
- if (progressCommentId) {
+ if (progressCommentId && !isNaN(Number(progressCommentId))) {
try {
const progressBody = [
'## ⚙️ SYSLOG 管道运行完成 ✅',
@@ -315,7 +315,7 @@ jobs:
body: progressBody
});
} catch (e) {
- console.log('⚠️ 更新进度评论失败: ' + e.message);
+ console.log('⚠️ 更新成功进度评论失败: ' + e.message);
}
}
@@ -368,7 +368,7 @@ jobs:
const actionsUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions';
// Update progress comment to reflect failure
- if (progressCommentId) {
+ if (progressCommentId && !isNaN(Number(progressCommentId))) {
try {
const progressBody = [
'## ⚙️ SYSLOG 管道运行异常 ❌',
@@ -388,7 +388,7 @@ jobs:
body: progressBody
});
} catch (e) {
- console.log('⚠️ 更新进度评论失败: ' + e.message);
+ console.log('⚠️ 更新失败进度评论失败: ' + e.message);
}
}
From f7a4898969705e1de4a623aa4fc6d3176d6d6402 Mon Sep 17 00:00:00 2001
From: bingshuo-neural-system
Date: Fri, 13 Mar 2026 14:51:46 +0000
Subject: [PATCH 047/116] =?UTF-8?q?=F0=9F=A7=A0=20=E5=86=B0=E6=9C=94?=
=?UTF-8?q?=E4=B8=BB=E6=8E=A7=E7=A5=9E=E7=BB=8F=E7=B3=BB=E7=BB=9F=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E7=BC=96=E8=AF=91=202026-03-13T14:51:46Z?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/bingshuo-issues-index.json | 2 +-
.github/brain/bingshuo-master-brain.md | 4 ++--
.github/brain/bingshuo-system-health.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index ba3c6ba5..01557422 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
- "updated_at": "2026-03-13T14:25:49.756Z",
+ "updated_at": "2026-03-13T14:51:45.942Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index a0f2c066..6bbc2224 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
-> 最后编译时间:2026-03-13T14:25:49.757Z
+> 最后编译时间:2026-03-13T14:51:45.943Z
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-13T14:25:49.757Z
+- **编译时间**:2026-03-13T14:51:45.943Z
- **脑文件规则版本**:v3.0
- **脑文件完整性**:✅ 完整
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index cec5ae04..53130964 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
- "updated_at": "2026-03-13T14:25:49.756Z",
+ "updated_at": "2026-03-13T14:51:45.942Z",
"health": {
"brain_consistency": {
"status": "yellow",
From 3fea6a6a62c5e41d6518a1c485fa27c487d20a7a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:51:56 +0000
Subject: [PATCH 048/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 81715518..305617ec 100644
--- a/README.md
+++ b/README.md
@@ -92,11 +92,11 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 22:27 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
-| 03-13 22:26 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
-| 03-13 22:26 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
-| 03-13 22:25 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
-| 03-13 22:01 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-13 22:51 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
+| 03-13 22:47 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
+| 03-13 22:38 | ❌ 🤖 铸渊巡检 Agent · 每日自动巡检与修复 · 失败 | 冰朔 |
+| 03-13 22:29 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
+| 03-13 22:29 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
| 03-13 21:55 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
@@ -112,9 +112,11 @@
### 🤖 铸渊自动提醒
-> 🟢 **今日无需冰朔手动干预** · 系统一切正常
+> 🔴 **需要冰朔手动干预!**
>
-> 🗓️ 2026-03-13 · 铸渊自动检测
+> - ❌ 🤖 铸渊巡检 Agent · 每日自动巡检与修复 · 失败
+>
+> 🗓️ 2026-03-13 · 铸渊已发送邮件提醒
---
@@ -128,9 +130,8 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 22:25 | 冰朔 | `—/` | ✅ 上传成功 |
-| 03-13 22:25 | Copilot | `—/` | ✅ 上传成功 |
-| 03-13 18:22 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
+| 03-13 22:51 | 冰朔 | `—/` | ✅ 上传成功 |
+| 03-13 22:51 | Copilot | `—/` | ✅ 上传成功 |
### 🤖 铸渊自动提醒 · 合作者
From 125b8b6faeae0902802040622334b3e968c4f441 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=93=B8=E6=B8=8A=20=28Zh=C3=B9Yu=C4=81n=29?=
Date: Fri, 13 Mar 2026 14:51:59 +0000
Subject: [PATCH 049/116] =?UTF-8?q?=F0=9F=93=9A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=9B=AE=E5=BD=95=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=20=C2=B7=202026-03-13T14:51?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/brain/repo-map.json | 4 ++--
.github/brain/repo-snapshot.md | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index defeafa7..0aa7b556 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T14:26:03.295Z",
+ "generated_at": "2026-03-13T14:51:59.358Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
@@ -400,7 +400,7 @@
},
{
"file": "syslog-issue-pipeline.yml",
- "name": "SYSLOG Issue Pipeline",
+ "name": "📡 SYSLOG Issue Pipeline",
"triggers": [
"issues"
]
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index f5f40464..d05918ac 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-13 22:26 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-13 22:51 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 40 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 22:26 CST |
+| 快照生成时间 | 2026-03-13 22:51 CST |
---
@@ -121,7 +121,7 @@
| `sync-login-entry.yml` | 铸渊 · Sync Login Entry · Notion → 飞书文档A | manual |
| `sync-persona-studio.yml` | 🔄 铸渊跨仓库同步 · persona-studio | push, manual |
| `syslog-auto-pipeline.yml` | SYSLOG Auto Pipeline | unknown |
-| `syslog-issue-pipeline.yml` | SYSLOG Issue Pipeline | issues |
+| `syslog-issue-pipeline.yml` | 📡 SYSLOG Issue Pipeline | issues |
| `syslog-pipeline.yml` | 铸渊 · SYSLOG Pipeline (A/D/E) | push, manual |
| `test-notion-bridge.yml` | "🧪 Notion Bridge Connectivity Test" | push, manual |
| `update-readme-bulletin.yml` | 📢 更新系统公告区 | push, schedule(0 1 * * *), manual |
From b17785d94cdbaaef0316b5d0d9e37d2a412c3126 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:53:26 +0000
Subject: [PATCH 050/116] =?UTF-8?q?=F0=9F=93=A2=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA?=
=?UTF-8?q?=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 305617ec..2c54ab29 100644
--- a/README.md
+++ b/README.md
@@ -92,11 +92,11 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
+| 03-13 22:53 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-13 22:52 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-13 22:51 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
| 03-13 22:51 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
-| 03-13 22:47 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
| 03-13 22:38 | ❌ 🤖 铸渊巡检 Agent · 每日自动巡检与修复 · 失败 | 冰朔 |
-| 03-13 22:29 | ✅ 铸渊 · Notion 工单轮询 · 成功 | 冰朔 |
-| 03-13 22:29 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
| 03-13 21:55 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-13 21:38 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
| 03-13 16:53 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
From 7ec8f2e1e173b01fa35f613261f77261b1a9ecf6 Mon Sep 17 00:00:00 2001
From: zhuyuan-bot
Date: Fri, 13 Mar 2026 15:30:20 +0000
Subject: [PATCH 051/116] =?UTF-8?q?=F0=9F=93=8A=20=E9=93=B8=E6=B8=8A?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0=E4=BC=9A=E8=AF=9D=E6=91=98?=
=?UTF-8?q?=E8=A6=81=20[skip=20ci]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
persona-telemetry/latest-summary.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/persona-telemetry/latest-summary.json b/persona-telemetry/latest-summary.json
index 4765d8db..848408dd 100644
--- a/persona-telemetry/latest-summary.json
+++ b/persona-telemetry/latest-summary.json
@@ -1,6 +1,6 @@
{
"version": "1.0",
- "timestamp": "2026-03-13T00:01:03.012Z",
+ "timestamp": "2026-03-13T15:30:19.982Z",
"sessions": {
"total_24h": 0,
"active_devs": [],
@@ -12,7 +12,7 @@
"style_profile": "通感语言·守护者",
"style_drift_score": 0,
"memory_depth": "0 selfchecks",
- "last_brain_update": "2026-03-11T10:24:00.000Z"
+ "last_brain_update": "2026-03-13T06:58:00.000Z"
},
"dev_progress": {
"syslog_submitted": 0,
From d7147274f8a4a2f1cf8ea9bfbd356aff9b925d9f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 15:34:38 +0000
Subject: [PATCH 052/116] Initial plan
From 2bd9a84abb5a895aab02d8fa115b7a2248035139 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 15:44:55 +0000
Subject: [PATCH 053/116] =?UTF-8?q?fix:=20replace=20static=20progress=20ta?=
=?UTF-8?q?ble=20with=20per-step=20comments=20+=20add=20intent=20routing?=
=?UTF-8?q?=20for=20@=E9=93=B8=E6=B8=8A=20queries?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bug A: syslog-issue-pipeline.yml now posts individual progress comments
for each step (1-8) instead of a static table that never updates.
zhuyuan-issue-reply.yml syslog-ack simplified to receipt confirmation.
Bug B: zhuyuan-issue-reply.js now uses intent-router.js to parse @铸渊
queries. Pipeline status queries (#92闭环状态) are routed correctly
instead of dumping team overview data.
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/syslog-issue-pipeline.yml | 250 ++++++++++++--------
.github/workflows/zhuyuan-issue-reply.yml | 19 +-
scripts/intent-router.js | 237 +++++++++++++++++++
scripts/pipeline-reporter.js | 140 +++++++++++
scripts/zhuyuan-issue-reply.js | 103 +++++---
5 files changed, 607 insertions(+), 142 deletions(-)
create mode 100644 scripts/intent-router.js
create mode 100644 scripts/pipeline-reporter.js
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index abac3fd3..8c262df8 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -56,42 +56,15 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts
- - name: 📋 进度通报 · 管道已启动
- id: progress
- uses: actions/github-script@v7
- with:
- script: |
- const number = context.payload.issue.number;
- const now = new Date().toISOString();
-
- const body = [
- '## ⚙️ SYSLOG 管道运行中',
- '',
- '> 管道启动时间: ' + now,
- '',
- '| 阶段 | 状态 | 说明 |',
- '|------|------|------|',
- '| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
- '| ② 解析内容 | 🔄 进行中 | 正在解析广播编号、邮箱、内容 |',
- '| ③ 模块验证 | ⏳ 等待中 | 检测模块上传状态 |',
- '| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 |',
- '| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单 |',
- '| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |',
- '| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到邮箱 |',
- '| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |',
- '',
- '> *管道正在运行,请勿关闭此 Issue*'
- ].join('\n');
-
- const { data: comment } = await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: number,
- body: body
- });
-
- core.setOutput('comment_id', comment.id);
- console.log('📋 进度通报已发送, comment_id=' + comment.id);
+ - name: 📋 步骤 1/8 · 接收提交
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '1'
+ STEP_NAME: '接收提交'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '系统已收到您的 SYSLOG 提交,管道正在运行中…'
+ run: node scripts/pipeline-reporter.js
- name: 🔍 Parse submission
id: parse
@@ -148,6 +121,28 @@ jobs:
console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`);
+ - name: 📋 步骤 2/8 · 解析内容(成功)
+ if: success()
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '2'
+ STEP_NAME: '解析内容'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '广播编号: `${{ steps.parse.outputs.broadcast_id }}` · 类型: ${{ steps.parse.outputs.type }} · 邮箱: ${{ steps.parse.outputs.email }}'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 2/8 · 解析内容(失败)
+ if: failure()
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '2'
+ STEP_NAME: '解析内容'
+ STEP_STATUS: 'error'
+ STEP_DETAIL: '解析失败,请检查提交格式是否正确(需包含广播编号、类型、邮箱、内容)'
+ run: node scripts/pipeline-reporter.js
+
- name: 🔍 铸渊 Agent · 模块上传验证
id: verify
env:
@@ -156,6 +151,28 @@ jobs:
AUTHOR: ${{ steps.parse.outputs.author }}
run: node scripts/verify-modules.js
+ - name: 📋 步骤 3/8 · 模块验证(成功)
+ if: always() && steps.verify.outcome == 'success'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '3'
+ STEP_NAME: '模块验证'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '模块上传验证已完成'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 3/8 · 模块验证(失败)
+ if: always() && steps.verify.outcome == 'failure'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '3'
+ STEP_NAME: '模块验证'
+ STEP_STATUS: 'error'
+ STEP_DETAIL: '模块验证异常(不影响闭环继续)'
+ run: node scripts/pipeline-reporter.js
+
- name: 🧠 Auto-detect and wake up persona
id: persona
env:
@@ -172,6 +189,28 @@ jobs:
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
+ - name: 📋 步骤 4/8 · 唤醒核心大脑(成功)
+ if: success()
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '4'
+ STEP_NAME: '唤醒核心大脑'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '铸渊核心大脑已完成处理'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 4/8 · 唤醒核心大脑(失败)
+ if: failure() && steps.parse.outcome == 'success'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '4'
+ STEP_NAME: '唤醒核心大脑'
+ STEP_STATUS: 'error'
+ STEP_DETAIL: 'API 调用失败,请检查 Secrets: LLM_API_KEY / LLM_BASE_URL 是否配置正确'
+ run: node scripts/pipeline-reporter.js
+
- name: 📋 创建标准化 Notion 工单(Phase B1)
id: ticket
if: ${{ secrets.NOTION_API_TOKEN }}
@@ -187,7 +226,41 @@ jobs:
SYSLOG_RAW: ${{ steps.parse.outputs.content }}
run: node scripts/create-standardized-ticket.js
+ - name: 📋 步骤 5/8 · 创建 Notion 工单(成功)
+ if: always() && steps.ticket.outcome == 'success'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '5'
+ STEP_NAME: '创建 Notion 工单'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '已写入霜砚工单簿'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 5/8 · 创建 Notion 工单(跳过)
+ if: always() && steps.ticket.outcome == 'skipped'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '5'
+ STEP_NAME: '创建 Notion 工单'
+ STEP_STATUS: 'skip'
+ STEP_DETAIL: 'Notion Token 未配置,跳过'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 5/8 · 创建 Notion 工单(失败)
+ if: always() && steps.ticket.outcome == 'failure'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '5'
+ STEP_NAME: '创建 Notion 工单'
+ STEP_STATUS: 'error'
+ STEP_DETAIL: 'Notion API 写入失败'
+ run: node scripts/pipeline-reporter.js
+
- name: 📡 推送广播到 GitHub(Phase B4)
+ id: broadcast
if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -196,7 +269,41 @@ jobs:
BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
run: node scripts/push-broadcast-to-github.js
+ - name: 📋 步骤 6/8 · 生成广播(成功)
+ if: always() && steps.broadcast.outcome == 'success'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '6'
+ STEP_NAME: '生成广播'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '新广播已生成并推送'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 6/8 · 生成广播(跳过)
+ if: always() && steps.broadcast.outcome == 'skipped'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '6'
+ STEP_NAME: '生成广播'
+ STEP_STATUS: 'skip'
+ STEP_DETAIL: '非 SYSLOG 类型或上游步骤未成功,跳过广播生成'
+ run: node scripts/pipeline-reporter.js
+
+ - name: 📋 步骤 6/8 · 生成广播(失败)
+ if: always() && steps.broadcast.outcome == 'failure'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '6'
+ STEP_NAME: '生成广播'
+ STEP_STATUS: 'error'
+ STEP_DETAIL: '广播生成或推送失败'
+ run: node scripts/pipeline-reporter.js
+
- name: 📧 Send email to developer
+ id: email
env:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
@@ -270,6 +377,17 @@ jobs:
});
"
+ - name: 📋 步骤 7/8 · 邮件通知
+ if: always() && steps.persona.outcome == 'success'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '7'
+ STEP_NAME: '邮件通知'
+ STEP_STATUS: 'ok'
+ STEP_DETAIL: '邮件通知步骤已执行'
+ run: node scripts/pipeline-reporter.js
+
- name: 💬 Reply to Issue
uses: actions/github-script@v7
with:
@@ -280,7 +398,6 @@ jobs:
const email = '${{ steps.parse.outputs.email }}';
const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
- const progressCommentId = '${{ steps.progress.outputs.comment_id }}';
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
const maskedEmail = email.length > 4
@@ -290,37 +407,8 @@ jobs:
? 'ℹ️ 未检测到模块引用'
: (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
- // Update progress comment to show all completed
- if (progressCommentId && !isNaN(Number(progressCommentId))) {
- try {
- const progressBody = [
- '## ⚙️ SYSLOG 管道运行完成 ✅',
- '',
- '| 阶段 | 状态 | 说明 |',
- '|------|------|------|',
- '| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
- '| ② 解析内容 | ✅ 已完成 | 广播编号: `' + broadcastId + '` |',
- '| ③ 模块验证 | ✅ 已完成 | ' + moduleStatus + ' |',
- '| ④ 唤醒核心大脑 | ✅ 已完成 | 铸渊核心大脑已处理 |',
- '| ⑤ 创建 Notion 工单 | ✅ 已完成 | 霜砚工单已推送 |',
- '| ⑥ 生成广播 | ✅ 已完成 | 新广播已生成 |',
- '| ⑦ 邮件通知 | ✅ 已完成 | 已发送至 `' + maskedEmail + '` |',
- '| ⑧ 闭环确认 | ✅ 已完成 | 见下方最终结果 |',
- ].join('\n');
-
- await github.rest.issues.updateComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: Number(progressCommentId),
- body: progressBody
- });
- } catch (e) {
- console.log('⚠️ 更新成功进度评论失败: ' + e.message);
- }
- }
-
const body = [
- '## ✅ 已处理 · ' + typeLabel,
+ '## 🎉 闭环处理完成 · ' + typeLabel,
'',
'| 项目 | 内容 |',
'|------|------|',
@@ -364,40 +452,14 @@ jobs:
const number = context.payload.issue?.number;
if (!number) return;
- const progressCommentId = '${{ steps.progress.outputs.comment_id }}';
const actionsUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions';
- // Update progress comment to reflect failure
- if (progressCommentId && !isNaN(Number(progressCommentId))) {
- try {
- const progressBody = [
- '## ⚙️ SYSLOG 管道运行异常 ❌',
- '',
- '| 阶段 | 状态 | 说明 |',
- '|------|------|------|',
- '| ① 接收提交 | ✅ 已完成 | 系统已收到 |',
- '| ② ~ ⑧ | ❌ 异常 | 管道处理过程中出错 |',
- '',
- '> 请检查提交格式或查看 [Actions 日志](' + actionsUrl + ')',
- ].join('\n');
-
- await github.rest.issues.updateComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: Number(progressCommentId),
- body: progressBody
- });
- } catch (e) {
- console.log('⚠️ 更新失败进度评论失败: ' + e.message);
- }
- }
-
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
- body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊(ICE-GL-ZY001)*'
+ body: '## ⚠️ 闭环处理异常\n\n❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 部分步骤异常,已记录日志。冰朔会在下次巡检时处理。\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊(ICE-GL-ZY001)*'
});
} catch (err) {
console.log('⚠️ 失败告警回复失败: ' + err.message);
diff --git a/.github/workflows/zhuyuan-issue-reply.yml b/.github/workflows/zhuyuan-issue-reply.yml
index 3d5eff6e..90b44c60 100644
--- a/.github/workflows/zhuyuan-issue-reply.yml
+++ b/.github/workflows/zhuyuan-issue-reply.yml
@@ -51,23 +51,10 @@ jobs:
'> **类型**: ' + submitType,
'> **接收时间**: ' + now,
'',
- '### 📋 处理进度',
+ '💡 SYSLOG 自动管道已启动,预计 2-5 分钟完成全部处理。',
+ '每完成一步都会在下方更新进度评论,你刷新页面即可看到最新状态。',
'',
- '| 阶段 | 状态 | 说明 |',
- '|------|------|------|',
- '| ① 接收提交 | ✅ 已完成 | 系统已收到你的 SYSLOG |',
- '| ② 解析内容 | ⏳ 等待中 | 解析广播编号、邮箱、内容 |',
- '| ③ 模块验证 | ⏳ 等待中 | 检测模块是否已上传到仓库 |',
- '| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 SYSLOG |',
- '| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单到 Notion |',
- '| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |',
- '| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到你的邮箱 |',
- '| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |',
- '',
- '> 💡 SYSLOG 自动管道已启动,预计 2-5 分钟完成全部处理。',
- '> 完成后本 Issue 下方会出现最终结果,你的邮箱也会收到通知。',
- '>',
- '> 如超过 10 分钟无后续回复,请在下方评论中 @铸渊 查询状态。',
+ '> 如超过 10 分钟无后续回复,请在下方评论中 `@铸渊 查询#' + number + '闭环状态`。',
'>',
'> *—— 铸渊(ICE-GL-ZY001)· 代码守护人格体*'
].join('\n');
diff --git a/scripts/intent-router.js b/scripts/intent-router.js
new file mode 100644
index 00000000..2ab49946
--- /dev/null
+++ b/scripts/intent-router.js
@@ -0,0 +1,237 @@
+/**
+ * ━━━ 意图路由模块 ━━━
+ * 文件位置:scripts/intent-router.js
+ *
+ * 解析用户在 Issue 评论中 @铸渊 的意图,路由到对应处理逻辑。
+ * 用于替换原来的「无脑倒全部数据」行为。
+ */
+
+const https = require('https');
+const fs = require('fs');
+
+/**
+ * 解析用户在 Issue 评论中 @铸渊 的意图
+ * @param {string} commentBody - 评论原文
+ * @returns {object} 意图对象
+ */
+function parseIntent(commentBody) {
+ // 去掉 @铸渊 前缀,提取核心内容
+ const content = commentBody.replace(/@铸渊[,,]?\s*/g, '').trim();
+
+ // 意图1:查询某个 Issue 的闭环状态
+ // 匹配:「查询#92闭环状态」「#92进度」「Issue 92 状态」「闭环状态」等
+ const issueMatch = content.match(/#(\d+)/);
+ if (issueMatch && /闭环|状态|进度|处理/.test(content)) {
+ return {
+ type: 'check_pipeline_status',
+ issueNumber: parseInt(issueMatch[1]),
+ raw: content
+ };
+ }
+
+ // 意图2:查询某个开发者的状态
+ // 匹配:「DEV-004状态」「之之进度」「查询肥猫」
+ const devMatch = content.match(/DEV-(\d+)|页页|肥猫|燕樊|之之|小草莓|花尔|桔子|匆匆那年|Awen|小兴|时雨/);
+ if (devMatch && /状态|进度|查询/.test(content)) {
+ return {
+ type: 'check_dev_status',
+ devId: devMatch[0],
+ raw: content
+ };
+ }
+
+ // 意图3:团队总览(显式请求)
+ // 匹配:「团队状态」「全部进度」「总览」
+ if (/团队|全部|总览|所有人/.test(content)) {
+ return {
+ type: 'team_overview',
+ raw: content
+ };
+ }
+
+ // 意图4:未识别 → 返回 unknown
+ return {
+ type: 'unknown',
+ raw: content
+ };
+}
+
+/**
+ * 查询 Issue 闭环管道状态(通过评论历史)
+ * @param {number} issueNumber - 要查询的 Issue 编号
+ * @returns {string} 格式化的状态回复
+ */
+async function checkPipelineStatus(issueNumber) {
+ const [owner, repo] = (process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab').split('/');
+
+ // 获取该 Issue 的评论历史
+ let comments;
+ try {
+ comments = await githubAPI('GET',
+ `/repos/${owner}/${repo}/issues/${issueNumber}/comments?per_page=100`);
+ } catch (e) {
+ return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` +
+ `查询失败: ${e.message}\n` +
+ `请确认 Issue 编号是否正确。`;
+ }
+
+ if (!Array.isArray(comments) || comments.length === 0) {
+ return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` +
+ `未找到该 Issue 的评论记录。\n` +
+ `可能原因:该 Issue 不存在或没有任何评论。`;
+ }
+
+ // 筛选铸渊的进度评论(包含「步骤 X/8」的)
+ const progressComments = comments.filter(c =>
+ c.body && c.body.includes('步骤') && c.body.includes('/8') && c.body.includes('铸渊')
+ );
+
+ // 检查是否有闭环完成汇总
+ const completeComment = comments.find(c =>
+ c.body && (c.body.includes('闭环处理完成') || c.body.includes('闭环处理异常'))
+ );
+
+ if (progressComments.length === 0 && !completeComment) {
+ // 检查是否有旧版静态进度表格
+ const staticTable = comments.find(c =>
+ c.body && c.body.includes('管道运行') && c.body.includes('等待中')
+ );
+
+ if (staticTable) {
+ return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` +
+ `⏳ 管道已启动但后续步骤未上报进度。\n` +
+ `可能原因:管道处理脚本未触发,或中间步骤出错。\n\n` +
+ `> 建议检查 Actions 运行日志。`;
+ }
+
+ return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` +
+ `未找到该 Issue 的闭环进度记录。\n` +
+ `可能原因:闭环尚未启动,或该 Issue 不是 SYSLOG 提交。`;
+ }
+
+ // 有进度评论:提取最新状态
+ if (completeComment) {
+ const isSuccess = completeComment.body.includes('闭环处理完成');
+ const hasFailed = comments.some(c => c.body && c.body.includes('❌'));
+
+ return `📋 **Issue #${issueNumber} 闭环状态**\n\n` +
+ `${isSuccess && !hasFailed ? '✅ 闭环已完成' : '⚠️ 闭环有步骤异常'}\n` +
+ `进度评论数: ${progressComments.length} 条\n` +
+ `最后更新: ${completeComment.created_at}`;
+ }
+
+ // 进行中
+ const lastProgress = progressComments[progressComments.length - 1];
+ const stepMatch = lastProgress.body.match(/步骤 (\d+)\/8/);
+ const lastStep = stepMatch ? parseInt(stepMatch[1]) : 0;
+ const hasFailed = progressComments.some(c => c.body.includes('❌'));
+
+ let statusLine;
+ if (hasFailed) {
+ statusLine = '❌ 闭环有步骤失败(详见该 Issue 下各步骤评论)';
+ } else {
+ statusLine = `⏳ 闭环进行中 · 已完成到步骤 ${lastStep}/8`;
+ }
+
+ return `📋 **Issue #${issueNumber} 闭环状态**\n\n` +
+ `${statusLine}\n` +
+ `进度评论数: ${progressComments.length} 条\n` +
+ `最后更新: ${lastProgress.created_at}`;
+}
+
+/**
+ * 查询指定开发者的状态
+ * @param {string} devIdOrName - DEV-XXX 或开发者昵称
+ * @param {object} devStatus - dev-status.json 数据
+ * @returns {string} 格式化的状态回复
+ */
+function checkDevStatus(devIdOrName, devStatus) {
+ const team = devStatus.team_status || devStatus.team || [];
+ const dev = team.find(d =>
+ d.dev_id === devIdOrName ||
+ d.name === devIdOrName ||
+ (d.dev_id && d.dev_id.toUpperCase() === devIdOrName.toUpperCase())
+ );
+
+ if (!dev) {
+ return `未找到开发者 ${devIdOrName} 的记录。`;
+ }
+
+ const modules = dev.modules
+ ? dev.modules.join('、')
+ : (dev.module || '未知');
+
+ return `📊 **${dev.dev_id} ${dev.name} · 当前状态**\n\n` +
+ `- 📌 模块: ${modules}\n` +
+ `- 📊 状态: ${dev.status}\n` +
+ `- ⏳ 等待中: ${dev.waiting_for || dev.waiting || '无'}\n` +
+ `- 👉 下一步: ${dev.next_step || dev.current || '无'}\n` +
+ `- 🔥 连胜: ${dev.streak || 0}`;
+}
+
+/**
+ * 团队总览
+ * @param {object} devStatus - dev-status.json 数据
+ * @returns {string} 格式化的总览
+ */
+function formatTeamOverview(devStatus) {
+ const team = devStatus.team_status || devStatus.team || [];
+ let reply = `## ⚒️ 铸渊回复 · 团队进度总览\n\n`;
+ team.forEach(dev => {
+ reply += `**${dev.dev_id} ${dev.name}** · ${dev.status}\n`;
+ });
+ reply += `\n---\n*最后同步:${devStatus.last_synced || devStatus.last_sync}*`;
+ return reply;
+}
+
+/**
+ * 生成「未识别意图」的帮助提示
+ * @returns {string}
+ */
+function unknownIntentHelp() {
+ return `🤔 我没理解你的问题。你可以试试:\n\n` +
+ `- \`@铸渊 查询#92闭环状态\` — 查某个 SYSLOG 提交的处理进度\n` +
+ `- \`@铸渊 DEV-004状态\` — 查某个开发者的进度\n` +
+ `- \`@铸渊 团队总览\` — 查所有人的状态`;
+}
+
+// ━━━ GitHub API 工具 ━━━
+function githubAPI(method, path, data) {
+ return new Promise((resolve, reject) => {
+ const body = data ? JSON.stringify(data) : null;
+ const req = https.request({
+ hostname: 'api.github.com',
+ path: path,
+ method: method,
+ headers: {
+ 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
+ 'Accept': 'application/vnd.github.v3+json',
+ 'User-Agent': 'Zhuyuan-Intent-Router',
+ 'Content-Type': 'application/json',
+ ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {})
+ }
+ }, (res) => {
+ let responseBody = '';
+ res.on('data', chunk => { responseBody += chunk; });
+ res.on('end', () => {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ try { resolve(JSON.parse(responseBody)); }
+ catch { resolve(responseBody); }
+ } else {
+ reject(new Error(`GitHub API ${res.statusCode}: ${responseBody.slice(0, 200)}`));
+ }
+ });
+ });
+ req.on('error', reject);
+ if (body) req.write(body);
+ req.end();
+ });
+}
+
+module.exports = {
+ parseIntent,
+ checkPipelineStatus,
+ checkDevStatus,
+ formatTeamOverview,
+ unknownIntentHelp
+};
diff --git a/scripts/pipeline-reporter.js b/scripts/pipeline-reporter.js
new file mode 100644
index 00000000..e11dabed
--- /dev/null
+++ b/scripts/pipeline-reporter.js
@@ -0,0 +1,140 @@
+/**
+ * ━━━ 实时进度上报模块 ━━━
+ * 文件位置:scripts/pipeline-reporter.js
+ *
+ * 每完成一步,在 Issue 评论区发一条新评论,开发者刷新即可看到实时进度。
+ *
+ * 使用方式(CLI):
+ * ISSUE_NUMBER=92 STEP_NUM=2 STEP_NAME="解析内容" STEP_STATUS=ok \
+ * STEP_DETAIL="广播编号: BC-M22-007-AW" GITHUB_TOKEN=xxx \
+ * node scripts/pipeline-reporter.js
+ *
+ * 或在 Node.js 中 require:
+ * const { reportStep, reportComplete } = require('./pipeline-reporter');
+ */
+
+const https = require('https');
+
+const OWNER = process.env.GITHUB_REPOSITORY
+ ? process.env.GITHUB_REPOSITORY.split('/')[0]
+ : 'qinfendebingshuo';
+const REPO = process.env.GITHUB_REPOSITORY
+ ? process.env.GITHUB_REPOSITORY.split('/')[1]
+ : 'guanghulab';
+
+/**
+ * 发送 GitHub API 请求
+ */
+function githubAPI(method, path, data) {
+ return new Promise((resolve, reject) => {
+ const body = data ? JSON.stringify(data) : null;
+ const req = https.request({
+ hostname: 'api.github.com',
+ path: path,
+ method: method,
+ headers: {
+ 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
+ 'Accept': 'application/vnd.github.v3+json',
+ 'User-Agent': 'Zhuyuan-Pipeline-Reporter',
+ 'Content-Type': 'application/json',
+ ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {})
+ }
+ }, (res) => {
+ let responseBody = '';
+ res.on('data', chunk => { responseBody += chunk; });
+ res.on('end', () => {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ try { resolve(JSON.parse(responseBody)); }
+ catch { resolve(responseBody); }
+ } else {
+ reject(new Error(`GitHub API ${res.statusCode}: ${responseBody.slice(0, 200)}`));
+ }
+ });
+ });
+ req.on('error', reject);
+ if (body) req.write(body);
+ req.end();
+ });
+}
+
+/**
+ * 每完成一步,在 Issue 评论区发一条新评论
+ * @param {number} issueNumber - Issue 编号
+ * @param {number} stepNum - 步骤编号(1-8)
+ * @param {string} stepName - 步骤名称
+ * @param {'ok'|'error'|'skip'} status - 步骤状态
+ * @param {string} detail - 详情说明
+ */
+async function reportStep(issueNumber, stepNum, stepName, status, detail) {
+ const icon = status === 'ok' ? '✅' : status === 'error' ? '❌' : '⏭️';
+ const statusText = status === 'ok' ? '已完成' : status === 'error' ? '失败' : '跳过';
+
+ const body = [
+ `${icon} **步骤 ${stepNum}/8 · ${stepName}** · ${statusText}`,
+ '',
+ detail,
+ '',
+ `——铸渊(ICE-GL-ZY001)· ${new Date().toISOString()}`
+ ].join('\n');
+
+ await githubAPI('POST', `/repos/${OWNER}/${REPO}/issues/${issueNumber}/comments`, { body });
+}
+
+/**
+ * 闭环完成时发最终汇总
+ * @param {number} issueNumber - Issue 编号
+ * @param {boolean} success - 是否全部成功
+ * @param {string} summary - 汇总内容
+ */
+async function reportComplete(issueNumber, success, summary) {
+ const icon = success ? '🎉' : '⚠️';
+ const title = success ? '闭环处理完成' : '闭环处理异常';
+
+ const body = [
+ `## ${icon} ${title}`,
+ '',
+ summary,
+ '',
+ success
+ ? '> 新广播已生成,结果将发送到您的邮箱。'
+ : '> 部分步骤异常,已记录日志。冰朔会在下次巡检时处理。',
+ '',
+ `——铸渊(ICE-GL-ZY001)· ${new Date().toISOString()}`
+ ].join('\n');
+
+ await githubAPI('POST', `/repos/${OWNER}/${REPO}/issues/${issueNumber}/comments`, { body });
+}
+
+// ━━━ CLI 模式:直接 node scripts/pipeline-reporter.js ━━━
+if (require.main === module) {
+ const issueNumber = parseInt(process.env.ISSUE_NUMBER);
+ const stepNum = parseInt(process.env.STEP_NUM);
+ const stepName = process.env.STEP_NAME || '';
+ const status = process.env.STEP_STATUS || 'ok';
+ const detail = process.env.STEP_DETAIL || '';
+ const isComplete = process.env.REPORT_COMPLETE === 'true';
+ const isSuccess = process.env.REPORT_SUCCESS !== 'false';
+
+ if (!issueNumber) {
+ console.error('❌ 缺少 ISSUE_NUMBER');
+ process.exit(1);
+ }
+
+ (async () => {
+ try {
+ if (isComplete) {
+ await reportComplete(issueNumber, isSuccess, detail);
+ console.log(`📋 闭环汇总已发送 · Issue #${issueNumber}`);
+ } else {
+ await reportStep(issueNumber, stepNum, stepName, status, detail);
+ console.log(`📋 步骤 ${stepNum}/8 · ${stepName} · ${status} 已上报 · Issue #${issueNumber}`);
+ }
+ } catch (err) {
+ console.error(`⚠️ 进度上报失败: ${err.message}`);
+ // 上报失败不应阻断管道
+ process.exit(0);
+ }
+ })();
+}
+
+module.exports = { reportStep, reportComplete };
diff --git a/scripts/zhuyuan-issue-reply.js b/scripts/zhuyuan-issue-reply.js
index 68089494..da558105 100644
--- a/scripts/zhuyuan-issue-reply.js
+++ b/scripts/zhuyuan-issue-reply.js
@@ -1,5 +1,6 @@
const fs = require('fs');
const https = require('https');
+const { parseIntent, checkPipelineStatus, checkDevStatus, formatTeamOverview, unknownIntentHelp } = require('./intent-router');
// === 知识库匹配阈值:问题词中至少40%出现在Issue文本中才算命中 ===
const FAQ_MATCH_THRESHOLD = 0.4;
@@ -166,53 +167,91 @@ async function handleCommentTrigger() {
return handleCollaboratorComment(user);
}
-// === 冰朔评论处理 ===
+// === 冰朔评论处理(含意图路由)===
async function handleBingshuoComment() {
console.log('🧊 冰朔指令识别中...');
- // 冰朔可以查询任何人的状态
- if (commentBody.includes('进度') || commentBody.includes('状态')) {
- if (devId && devInfo) {
- const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n`
- + `**${devInfo.name}(${devInfo.dev_id})当前状态:**\n`
- + `- 📌 模块:${devInfo.modules.join('、')}\n`
- + `- 📊 状态:${devInfo.status}\n`
- + `- ⏳ 等待中:${devInfo.waiting_for}\n`
- + `- 👉 下一步:${devInfo.next_step}\n\n`
- + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n`
+ // 使用意图路由器解析评论
+ const intent = parseIntent(commentBody);
+ console.log('[意图识别]', JSON.stringify(intent));
+
+ switch (intent.type) {
+ case 'check_pipeline_status': {
+ // 查询某个 Issue 的闭环管道状态
+ const statusReply = await checkPipelineStatus(intent.issueNumber);
+ const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n${statusReply}\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ case 'check_dev_status': {
+ // 查询某个开发者的状态
+ const devReply = checkDevStatus(intent.devId, devStatus);
+ const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n${devReply}\n\n`
+ + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced || devStatus.last_sync}*\n`
+ `*—— 铸渊(ICE-GL-ZY001)*`;
await postComment(reply);
return;
}
- // 团队总览
- let reply = `## ⚒️ 铸渊回复 · 团队进度总览\n\n`;
- devStatus.team_status.forEach(dev => {
- reply += `**${dev.dev_id} ${dev.name}** · ${dev.status}\n`;
- });
- reply += `\n---\n*最后同步:${devStatus.last_synced}*\n`;
- reply += `*—— 铸渊(ICE-GL-ZY001)*`;
- await postComment(reply);
- return;
- }
+ case 'team_overview': {
+ // 显式请求团队总览
+ const reply = formatTeamOverview(devStatus)
+ + `\n*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
- // 冰朔的一般指令 → 用 AI 处理
- const aiReply = await callYunwuAPI('冰朔指令', commentBody, null);
- if (aiReply) {
- const reply = `## ⚒️ 铸渊回复 · 冰朔\n\n${aiReply}\n\n`
- + `---\n*—— 铸渊(ICE-GL-ZY001)· 冰朔指令已处理*`;
- await postComment(reply);
- } else {
- const reply = `## ⚒️ 铸渊收到\n\n冰朔,已记录你的指令。铸渊会在下次巡检时处理。\n\n`
- + `---\n*—— 铸渊(ICE-GL-ZY001)*`;
- await postComment(reply);
+ default: {
+ // 未识别的意图:尝试 AI 回答,或返回帮助提示
+ // 冰朔可以查询任何人的状态(兼容旧逻辑)
+ if ((commentBody.includes('进度') || commentBody.includes('状态')) && devId && devInfo) {
+ const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n`
+ + `**${devInfo.name}(${devInfo.dev_id})当前状态:**\n`
+ + `- 📌 模块:${devInfo.modules.join('、')}\n`
+ + `- 📊 状态:${devInfo.status}\n`
+ + `- ⏳ 等待中:${devInfo.waiting_for}\n`
+ + `- 👉 下一步:${devInfo.next_step}\n\n`
+ + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n`
+ + `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 冰朔的一般指令 → 用 AI 处理
+ const aiReply = await callYunwuAPI('冰朔指令', commentBody, null);
+ if (aiReply) {
+ const reply = `## ⚒️ 铸渊回复 · 冰朔\n\n${aiReply}\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)· 冰朔指令已处理*`;
+ await postComment(reply);
+ } else {
+ const helpText = unknownIntentHelp();
+ const reply = `## ⚒️ 铸渊收到\n\n冰朔,已记录你的指令。\n\n${helpText}\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ }
+ }
}
}
-// === 合作者评论处理 ===
+// === 合作者评论处理(含意图路由)===
async function handleCollaboratorComment(user) {
console.log(`👤 合作者 ${user.name}(${user.devId})问题处理中...`);
+ // 使用意图路由器解析评论
+ const intent = parseIntent(commentBody);
+ console.log('[意图识别]', JSON.stringify(intent));
+
+ // 合作者查询闭环管道状态
+ if (intent.type === 'check_pipeline_status') {
+ const statusReply = await checkPipelineStatus(intent.issueNumber);
+ const reply = `## ⚒️ 铸渊回复\n\n${statusReply}\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
// 合作者只能查询自己的状态
const selfDevInfo = user.devId
? devStatus.team_status.find(d => d.dev_id === user.devId)
From 8b013c998f137fed6354e37e4c9c770237c88993 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 15:46:41 +0000
Subject: [PATCH 054/116] fix: address code review feedback - fix empty catch
blocks, remove redundant fallback, fix email step status
- Add error logging to catch blocks in intent-router.js and pipeline-reporter.js
- Remove redundant devId+devInfo fallback in handleBingshuoComment (now handled by parseIntent)
- Fix email step progress reporting to use steps.email.outcome instead of steps.persona.outcome
- Standardize last_synced field access
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/syslog-issue-pipeline.yml | 15 +++++++++++++--
scripts/intent-router.js | 2 +-
scripts/pipeline-reporter.js | 2 +-
scripts/zhuyuan-issue-reply.js | 17 +----------------
4 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index 8c262df8..c7386478 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -377,8 +377,8 @@ jobs:
});
"
- - name: 📋 步骤 7/8 · 邮件通知
- if: always() && steps.persona.outcome == 'success'
+ - name: 📋 步骤 7/8 · 邮件通知(成功)
+ if: always() && steps.email.outcome == 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
@@ -388,6 +388,17 @@ jobs:
STEP_DETAIL: '邮件通知步骤已执行'
run: node scripts/pipeline-reporter.js
+ - name: 📋 步骤 7/8 · 邮件通知(失败)
+ if: always() && steps.email.outcome == 'failure'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ STEP_NUM: '7'
+ STEP_NAME: '邮件通知'
+ STEP_STATUS: 'error'
+ STEP_DETAIL: '邮件发送失败,请检查 Secrets: SMTP_USER / SMTP_PASS'
+ run: node scripts/pipeline-reporter.js
+
- name: 💬 Reply to Issue
uses: actions/github-script@v7
with:
diff --git a/scripts/intent-router.js b/scripts/intent-router.js
index 2ab49946..d78c7fac 100644
--- a/scripts/intent-router.js
+++ b/scripts/intent-router.js
@@ -216,7 +216,7 @@ function githubAPI(method, path, data) {
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try { resolve(JSON.parse(responseBody)); }
- catch { resolve(responseBody); }
+ catch (e) { console.error('⚠️ JSON parse error:', e.message); resolve(responseBody); }
} else {
reject(new Error(`GitHub API ${res.statusCode}: ${responseBody.slice(0, 200)}`));
}
diff --git a/scripts/pipeline-reporter.js b/scripts/pipeline-reporter.js
index e11dabed..74fa4eb6 100644
--- a/scripts/pipeline-reporter.js
+++ b/scripts/pipeline-reporter.js
@@ -45,7 +45,7 @@ function githubAPI(method, path, data) {
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try { resolve(JSON.parse(responseBody)); }
- catch { resolve(responseBody); }
+ catch (e) { console.error('⚠️ JSON parse error:', e.message); resolve(responseBody); }
} else {
reject(new Error(`GitHub API ${res.statusCode}: ${responseBody.slice(0, 200)}`));
}
diff --git a/scripts/zhuyuan-issue-reply.js b/scripts/zhuyuan-issue-reply.js
index da558105..f14e9bc4 100644
--- a/scripts/zhuyuan-issue-reply.js
+++ b/scripts/zhuyuan-issue-reply.js
@@ -189,7 +189,7 @@ async function handleBingshuoComment() {
// 查询某个开发者的状态
const devReply = checkDevStatus(intent.devId, devStatus);
const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n${devReply}\n\n`
- + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced || devStatus.last_sync}*\n`
+ + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n`
+ `*—— 铸渊(ICE-GL-ZY001)*`;
await postComment(reply);
return;
@@ -204,21 +204,6 @@ async function handleBingshuoComment() {
}
default: {
- // 未识别的意图:尝试 AI 回答,或返回帮助提示
- // 冰朔可以查询任何人的状态(兼容旧逻辑)
- if ((commentBody.includes('进度') || commentBody.includes('状态')) && devId && devInfo) {
- const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n`
- + `**${devInfo.name}(${devInfo.dev_id})当前状态:**\n`
- + `- 📌 模块:${devInfo.modules.join('、')}\n`
- + `- 📊 状态:${devInfo.status}\n`
- + `- ⏳ 等待中:${devInfo.waiting_for}\n`
- + `- 👉 下一步:${devInfo.next_step}\n\n`
- + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n`
- + `*—— 铸渊(ICE-GL-ZY001)*`;
- await postComment(reply);
- return;
- }
-
// 冰朔的一般指令 → 用 AI 处理
const aiReply = await callYunwuAPI('冰朔指令', commentBody, null);
if (aiReply) {
From 0534c4e1fe4c9be57d7e54703af64f63da663771 Mon Sep 17 00:00:00 2001
From: juzi0412 <1824680224@qq.com>
Date: Sat, 14 Mar 2026 01:56:47 +0800
Subject: [PATCH 055/116] =?UTF-8?q?[BC-M-CHANNEL-006-JZ][DEV-010]=20?=
=?UTF-8?q?=E7=8E=AF=E8=8A=828=20=E9=A2=91=E9=81=93=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E9=9D=A2=E6=9D=BF=E4=B8=8E=E6=80=A7=E8=83=BD=E7=9B=91=E6=8E=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
channel-enhancements.js | 318 ++++
channel-router.js | 153 ++
event-bus.js | 55 +
index.html | 0
module-lifecycle.js | 90 ++
modules/m-channel/adapters/m06-adapter.js | 61 +
modules/m-channel/adapters/m08-adapter.js | 47 +
modules/m-channel/adapters/m11-adapter.js | 47 +
modules/m-channel/adapters/module-adapter.js | 140 ++
modules/m-channel/app.js | 85 +-
modules/m-channel/backup-混乱版/app.js | 42 +
.../backup-混乱版/channel-router-backup.js | 98 ++
.../m-channel/backup-混乱版/channel-router.js | 267 ++++
.../m-channel/backup-混乱版/channel-state.js | 149 ++
.../m-channel/backup-混乱版/channel-style.css | 132 ++
.../backup-混乱版/channel-transition.css | 83 +
modules/m-channel/backup-混乱版/event-bus.js | 111 ++
modules/m-channel/backup-混乱版/index.html | 33 +
.../backup-混乱版/module-lifecycle.js | 90 ++
.../m-channel/backup-混乱版/module-loader.js | 116 ++
.../backup-混乱版/module-registry.js | 7 +
modules/m-channel/channel-analytics.js | 163 ++
modules/m-channel/channel-complete.html | 267 ++++
modules/m-channel/channel-dashboard.css | 199 +++
modules/m-channel/channel-dashboard.js | 201 +++
modules/m-channel/channel-enhancements.js | 338 ++++
modules/m-channel/channel-favorites.js | 271 ++++
modules/m-channel/channel-layout.css | 145 ++
modules/m-channel/channel-notifications.css | 200 +++
modules/m-channel/channel-notifications.js | 365 +++++
modules/m-channel/channel-preferences.js | 194 +++
modules/m-channel/channel-router.js | 302 ++--
modules/m-channel/channel-router.js.bak | 190 +++
modules/m-channel/channel-router.js.bakcat | 0
modules/m-channel/channel-state.js | 44 +
modules/m-channel/channel-stats.js | 298 ++++
modules/m-channel/channel-style.css | 188 +--
modules/m-channel/channel-theme.js | 157 ++
modules/m-channel/channel-transition.css | 48 +
modules/m-channel/channel-ultimate.html | 1401 +++++++++++++++++
modules/m-channel/error-boundary.js | 134 ++
modules/m-channel/event-bus.js | 37 +
modules/m-channel/index.html | 47 +-
modules/m-channel/index.html.bak | 33 +
modules/m-channel/mock-modules/mock-a.html | 21 +-
modules/m-channel/mock-modules/mock-b.html | 22 +-
modules/m-channel/mock-modules/mock-c.html | 5 -
modules/m-channel/mock-modules/mock-d.html | 5 -
modules/m-channel/module-lifecycle.js | 23 +
modules/m-channel/module-loader.js | 93 +-
modules/m-channel/module-registry.js | 22 +-
modules/m-channel/views/404.html | 5 -
modules/m-channel/views/about.html | 8 -
.../m-channel/views/channel-dashboard.html | 57 +
modules/m-channel/views/channel-debug.html | 93 ++
modules/m-channel/views/channel-settings.html | 272 ++++
modules/m-channel/views/channel.html | 29 -
modules/m-channel/views/home.html | 12 -
58 files changed, 7586 insertions(+), 427 deletions(-)
create mode 100644 channel-enhancements.js
create mode 100644 channel-router.js
create mode 100644 event-bus.js
create mode 100644 index.html
create mode 100644 module-lifecycle.js
create mode 100644 modules/m-channel/adapters/m06-adapter.js
create mode 100644 modules/m-channel/adapters/m08-adapter.js
create mode 100644 modules/m-channel/adapters/m11-adapter.js
create mode 100644 modules/m-channel/adapters/module-adapter.js
create mode 100644 modules/m-channel/backup-混乱版/app.js
create mode 100644 modules/m-channel/backup-混乱版/channel-router-backup.js
create mode 100644 modules/m-channel/backup-混乱版/channel-router.js
create mode 100644 modules/m-channel/backup-混乱版/channel-state.js
create mode 100644 modules/m-channel/backup-混乱版/channel-style.css
create mode 100644 modules/m-channel/backup-混乱版/channel-transition.css
create mode 100644 modules/m-channel/backup-混乱版/event-bus.js
create mode 100644 modules/m-channel/backup-混乱版/index.html
create mode 100644 modules/m-channel/backup-混乱版/module-lifecycle.js
create mode 100644 modules/m-channel/backup-混乱版/module-loader.js
create mode 100644 modules/m-channel/backup-混乱版/module-registry.js
create mode 100644 modules/m-channel/channel-analytics.js
create mode 100644 modules/m-channel/channel-complete.html
create mode 100644 modules/m-channel/channel-dashboard.css
create mode 100644 modules/m-channel/channel-dashboard.js
create mode 100644 modules/m-channel/channel-enhancements.js
create mode 100644 modules/m-channel/channel-favorites.js
create mode 100644 modules/m-channel/channel-layout.css
create mode 100644 modules/m-channel/channel-notifications.css
create mode 100644 modules/m-channel/channel-notifications.js
create mode 100644 modules/m-channel/channel-preferences.js
create mode 100644 modules/m-channel/channel-router.js.bak
create mode 100644 modules/m-channel/channel-router.js.bakcat
create mode 100644 modules/m-channel/channel-state.js
create mode 100644 modules/m-channel/channel-stats.js
create mode 100644 modules/m-channel/channel-theme.js
create mode 100644 modules/m-channel/channel-transition.css
create mode 100644 modules/m-channel/channel-ultimate.html
create mode 100644 modules/m-channel/error-boundary.js
create mode 100644 modules/m-channel/event-bus.js
create mode 100644 modules/m-channel/index.html.bak
delete mode 100644 modules/m-channel/mock-modules/mock-c.html
delete mode 100644 modules/m-channel/mock-modules/mock-d.html
create mode 100644 modules/m-channel/module-lifecycle.js
delete mode 100644 modules/m-channel/views/404.html
delete mode 100644 modules/m-channel/views/about.html
create mode 100644 modules/m-channel/views/channel-dashboard.html
create mode 100644 modules/m-channel/views/channel-debug.html
create mode 100644 modules/m-channel/views/channel-settings.html
delete mode 100644 modules/m-channel/views/channel.html
delete mode 100644 modules/m-channel/views/home.html
diff --git a/channel-enhancements.js b/channel-enhancements.js
new file mode 100644
index 00000000..8451f9f0
--- /dev/null
+++ b/channel-enhancements.js
@@ -0,0 +1,318 @@
+// channel-enhancements.js - 频道搜索、面包屑、快捷键增强功能(修复版)
+(function() {
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+
+ function init() {
+ addStyles();
+ createSearchBox();
+ createBreadcrumb();
+ createShortcutHint();
+ initShortcuts();
+ initSearch();
+ window.addEventListener('hashchange', updateBreadcrumb);
+ updateBreadcrumb();
+ }
+
+ function addStyles() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .channel-search-container {
+ padding: 16px 20px;
+ background: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+ .channel-search-input {
+ flex: 1;
+ padding: 10px 16px;
+ border: 1px solid #ced4da;
+ border-radius: 24px;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.2s;
+ }
+ .channel-search-input:focus {
+ border-color: #4d6bfe;
+ box-shadow: 0 0 0 3px rgba(77,107,254,0.1);
+ }
+ .channel-search-clear {
+ background: none;
+ border: none;
+ font-size: 18px;
+ cursor: pointer;
+ color: #6c757d;
+ padding: 0 8px;
+ display: none;
+ }
+ .channel-search-clear:hover {
+ color: #212529;
+ }
+ .channel-search-input:not(:placeholder-shown) + .channel-search-clear {
+ display: inline-block;
+ }
+ .highlight {
+ background-color: #ffeb3b;
+ padding: 2px 0;
+ border-radius: 2px;
+ }
+ .no-results {
+ text-align: center;
+ padding: 40px;
+ color: #6c757d;
+ font-style: italic;
+ }
+ .channel-breadcrumb {
+ padding: 12px 20px;
+ background: white;
+ border-bottom: 1px solid #e9ecef;
+ font-size: 14px;
+ }
+ .channel-breadcrumb a {
+ color: #4d6bfe;
+ text-decoration: none;
+ }
+ .channel-breadcrumb a:hover {
+ text-decoration: underline;
+ }
+ .channel-breadcrumb span {
+ color: #6c757d;
+ }
+ .channel-breadcrumb .current {
+ color: #212529;
+ font-weight: 500;
+ }
+ .shortcut-hint {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background: rgba(0,0,0,0.7);
+ color: white;
+ padding: 8px 16px;
+ border-radius: 30px;
+ font-size: 12px;
+ backdrop-filter: blur(4px);
+ z-index: 1000;
+ }
+ .shortcut-hint kbd {
+ background: rgba(255,255,255,0.2);
+ padding: 2px 6px;
+ border-radius: 4px;
+ margin: 0 2px;
+ }
+ .module-card.selected {
+ outline: 2px solid #4d6bfe;
+ outline-offset: 2px;
+ transform: scale(1.02);
+ transition: all 0.2s;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ function createSearchBox() {
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.querySelector('.channel-content');
+ if (!container) return;
+
+ const searchContainer = document.createElement('div');
+ searchContainer.className = 'channel-search-container';
+ searchContainer.innerHTML = `
+
+
+ `;
+ container.parentNode.insertBefore(searchContainer, container);
+
+ const searchInput = searchContainer.querySelector('.channel-search-input');
+ const clearBtn = searchContainer.querySelector('.channel-search-clear');
+
+ searchInput.addEventListener('input', function() {
+ filterModules(this.value);
+ });
+
+ clearBtn.addEventListener('click', function() {
+ searchInput.value = '';
+ filterModules('');
+ searchInput.focus();
+ });
+
+ window.__channelSearchInput = searchInput;
+ }
+
+ function filterModules(keyword) {
+ const cards = document.querySelectorAll('.module-card');
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
+ let hasResults = false;
+
+ // 先移除所有高亮
+ removeAllHighlights();
+
+ cards.forEach(card => {
+ const text = card.innerText || card.textContent;
+ if (keyword === '') {
+ card.style.display = '';
+ hasResults = true;
+ } else {
+ const lowerText = text.toLowerCase();
+ const lowerKeyword = keyword.toLowerCase();
+ if (lowerText.includes(lowerKeyword)) {
+ card.style.display = '';
+ highlightText(card, keyword);
+ hasResults = true;
+ } else {
+ card.style.display = 'none';
+ }
+ }
+ });
+
+ let noResultsEl = document.querySelector('.no-results');
+ if (!hasResults && keyword !== '') {
+ if (!noResultsEl) {
+ noResultsEl = document.createElement('div');
+ noResultsEl.className = 'no-results';
+ noResultsEl.textContent = '没有找到匹配的模块';
+ container.parentNode.insertBefore(noResultsEl, container.nextSibling);
+ }
+ } else {
+ if (noResultsEl) noResultsEl.remove();
+ }
+ }
+
+ // 移除所有高亮span,恢复原文本
+ function removeAllHighlights() {
+ document.querySelectorAll('.highlight').forEach(span => {
+ const parent = span.parentNode;
+ parent.replaceChild(document.createTextNode(span.textContent), span);
+ parent.normalize(); // 合并相邻文本节点
+ });
+ }
+
+ // 高亮匹配文本(不破坏事件监听)
+ function highlightText(card, keyword) {
+ const regex = new RegExp(`(${keyword})`, 'gi');
+ const walk = document.createTreeWalker(card, NodeFilter.SHOW_TEXT, {
+ acceptNode: function(node) {
+ // 跳过已经高亮过的span内部
+ if (node.parentNode.classList && node.parentNode.classList.contains('highlight')) {
+ return NodeFilter.FILTER_REJECT;
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ }, false);
+
+ const textNodes = [];
+ while (walk.nextNode()) textNodes.push(walk.currentNode);
+
+ textNodes.forEach(node => {
+ const text = node.nodeValue;
+ if (regex.test(text)) {
+ const span = document.createElement('span');
+ span.className = 'highlight';
+ span.innerHTML = text.replace(regex, '$1');
+ node.parentNode.replaceChild(span, node);
+ }
+ });
+ }
+
+ function createBreadcrumb() {
+ const searchContainer = document.querySelector('.channel-search-container');
+ const breadcrumb = document.createElement('div');
+ breadcrumb.className = 'channel-breadcrumb';
+ breadcrumb.id = 'channelBreadcrumb';
+ if (searchContainer) {
+ searchContainer.parentNode.insertBefore(breadcrumb, searchContainer.nextSibling);
+ } else {
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
+ if (container) {
+ container.parentNode.insertBefore(breadcrumb, container);
+ }
+ }
+ }
+
+ function updateBreadcrumb() {
+ const breadcrumb = document.getElementById('channelBreadcrumb');
+ if (!breadcrumb) return;
+ const hash = window.location.hash.slice(1) || '';
+ let moduleName = '频道';
+ if (hash.startsWith('module-')) {
+ const moduleId = hash.replace('module-', '');
+ const moduleNames = {
+ 'M06': '工单管理',
+ 'M08': '数据看板',
+ 'M11': '用户反馈'
+ };
+ moduleName = moduleNames[moduleId] || moduleId;
+ }
+ breadcrumb.innerHTML = `
+ 首页 >
+ 频道 >
+ ${moduleName}
+ `;
+ }
+
+ function createShortcutHint() {
+ const hint = document.createElement('div');
+ hint.className = 'shortcut-hint';
+ hint.innerHTML = `
+ ⌘K 搜索 ·
+ ↑↓ 选择 ·
+ Enter 打开 ·
+ Esc 关闭
+ `;
+ document.body.appendChild(hint);
+ }
+
+ function initShortcuts() {
+ document.addEventListener('keydown', function(e) {
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
+ e.preventDefault();
+ const searchInput = window.__channelSearchInput;
+ if (searchInput) searchInput.focus();
+ }
+ if (e.key === 'Escape') {
+ const searchInput = window.__channelSearchInput;
+ if (searchInput && document.activeElement === searchInput) {
+ searchInput.value = '';
+ filterModules('');
+ searchInput.blur();
+ } else if (searchInput && searchInput.value) {
+ searchInput.value = '';
+ filterModules('');
+ }
+ }
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
+ const cards = Array.from(document.querySelectorAll('.module-card:not([style*="display: none"])'));
+ if (cards.length === 0) return;
+ e.preventDefault();
+ let selectedIndex = cards.findIndex(card => card.classList.contains('selected'));
+ if (selectedIndex === -1) {
+ selectedIndex = e.key === 'ArrowDown' ? 0 : cards.length - 1;
+ } else {
+ cards[selectedIndex].classList.remove('selected');
+ if (e.key === 'ArrowDown') {
+ selectedIndex = (selectedIndex + 1) % cards.length;
+ } else {
+ selectedIndex = (selectedIndex - 1 + cards.length) % cards.length;
+ }
+ }
+ cards[selectedIndex].classList.add('selected');
+ cards[selectedIndex].scrollIntoView({ block: 'nearest' });
+ }
+ if (e.key === 'Enter') {
+ const selected = document.querySelector('.module-card.selected');
+ if (selected) {
+ // 触发点击事件,模拟鼠标点击
+ selected.click();
+ }
+ }
+ });
+ }
+
+ function initSearch() {
+ // 不需要预先保存innerHTML了
+ }
+})();
diff --git a/channel-router.js b/channel-router.js
new file mode 100644
index 00000000..14dbf562
--- /dev/null
+++ b/channel-router.js
@@ -0,0 +1,153 @@
+// ================== 路由配置 ==================
+const routes = {
+ 'home': 'views/home.html',
+ 'channel': 'views/channel.html',
+ 'about': 'views/about.html'
+};
+
+// 动画时长
+const ANIMATION_DURATION = 300;
+
+// 获取当前 hash 中的路径(去掉 #/)
+function getHashPath() {
+ const hash = window.location.hash.slice(1) || '/';
+ const path = hash.startsWith('/') ? hash.slice(1) : hash;
+ return path || 'home';
+}
+
+// ================== 状态管理(localStorage) ==================
+const STORAGE_KEY = 'm-channel-state';
+
+// 保存状态
+function saveState(path) {
+ // 获取已访问模块列表
+ let visited = JSON.parse(localStorage.getItem(STORAGE_KEY)) || { visitedModules: [] };
+ if (!visited.visitedModules.includes(path)) {
+ visited.visitedModules.push(path);
+ }
+ visited.lastRoute = path;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(visited));
+ console.log(`[state] save ${path}`, visited);
+}
+
+// 恢复状态(返回上次访问的路由,如果没有则返回 'home')
+function restoreState() {
+ const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
+ if (saved && saved.lastRoute) {
+ console.log(`[state] restore ${saved.lastRoute}`, saved);
+ return saved.lastRoute;
+ }
+ return 'home';
+}
+
+// ================== 加载视图(带动画+状态保存) ==================
+async function loadView(path) {
+ const routerView = document.getElementById('router-view');
+ if (!routerView) return;
+
+ // 1. 开始离开动画
+ routerView.classList.add('fade-leave-active', 'fade-leave-to');
+
+ // 2. 稍等片刻让离开动画跑起来,再加载新内容
+ setTimeout(async () => {
+ routerView.innerHTML = '';
+
+ try {
+ const viewFile = routes[path];
+ if (!viewFile) {
+ await load404(routerView);
+ return;
+ }
+
+ const response = await fetch(viewFile);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ const html = await response.text();
+ routerView.innerHTML = html;
+ } catch (error) {
+ console.error('加载视图失败:', error);
+ routerView.innerHTML = `
+
+ ❌ 加载失败:${error.message}
+ 请检查文件是否存在,或刷新重试
+
+ `;
+ }
+
+ // 3. 移除离开动画类,添加入场动画类
+ routerView.classList.remove('fade-leave-active', 'fade-leave-to');
+ routerView.classList.add('fade-enter-active', 'fade-enter-from');
+
+ requestAnimationFrame(() => {
+ routerView.classList.remove('fade-enter-from');
+ });
+
+ setTimeout(() => {
+ routerView.classList.remove('fade-enter-active');
+ }, ANIMATION_DURATION);
+
+ // 4. 保存状态到 localStorage(并打印日志)
+ saveState(path);
+
+ // 5. 更新导航高亮和状态栏
+ updateActiveNav(path);
+ updateStatusBar(path);
+ }, 50);
+}
+
+// 加载 404 页面
+async function load404(container) {
+ try {
+ const resp = await fetch('views/404.html');
+ if (resp.ok) {
+ container.innerHTML = await resp.text();
+ } else {
+ container.innerHTML = '⚠️ 404 - 页面未找到
';
+ }
+ } catch {
+ container.innerHTML = '⚠️ 404 - 页面未找到
';
+ }
+}
+
+// 更新导航高亮
+function updateActiveNav(path) {
+ document.querySelectorAll('.nav-link').forEach(link => {
+ link.classList.remove('active');
+ const linkPath = link.getAttribute('href').slice(2);
+ if (linkPath === path) {
+ link.classList.add('active');
+ }
+ });
+}
+
+// 更新状态栏
+function updateStatusBar(path) {
+ const statusEl = document.getElementById('current-route');
+ if (statusEl) {
+ statusEl.textContent = `当前路由:/${path}`;
+ }
+}
+
+// ================== 初始化:恢复上次访问的路由 ==================
+function initRouter() {
+ // 先尝试从 localStorage 恢复上次路由
+ const lastRoute = restoreState();
+ // 如果当前 hash 为空或为默认,就跳转到上次路由
+ if (!window.location.hash || window.location.hash === '#/home') {
+ window.location.hash = `#/${lastRoute}`;
+ } else {
+ // 否则加载当前 hash 对应的路由
+ const path = getHashPath();
+ loadView(path);
+ }
+}
+
+// 监听 hash 变化
+window.addEventListener('hashchange', () => {
+ const path = getHashPath();
+ loadView(path);
+});
+
+// 首次加载
+window.addEventListener('DOMContentLoaded', initRouter);
diff --git a/event-bus.js b/event-bus.js
new file mode 100644
index 00000000..c7d436fa
--- /dev/null
+++ b/event-bus.js
@@ -0,0 +1,55 @@
+// event-bus.js
+// 事件总线(发布/订阅模式)
+
+const EventBus = {
+ events: {},
+
+ // 订阅事件
+ on(eventName, callback) {
+ if (!this.events[eventName]) {
+ this.events[eventName] = [];
+ }
+ this.events[eventName].push(callback);
+ console.log(`[bus] 订阅事件: ${eventName}`);
+ return this; // 支持链式调用
+ },
+
+ // 发布事件
+ emit(eventName, data) {
+ console.log(`[bus] 发布事件: ${eventName}`, data);
+ if (this.events[eventName]) {
+ this.events[eventName].forEach(callback => {
+ try {
+ callback(data);
+ } catch (err) {
+ console.error(`[bus] 事件 ${eventName} 回调执行错误:`, err);
+ }
+ });
+ }
+ return this;
+ },
+
+ // 取消订阅
+ off(eventName, callback) {
+ if (!this.events[eventName]) return this;
+ if (!callback) {
+ // 如果没有提供回调,取消该事件的所有订阅
+ delete this.events[eventName];
+ console.log(`[bus] 取消所有订阅: ${eventName}`);
+ } else {
+ // 移除特定的回调
+ this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
+ console.log(`[bus] 取消一个订阅: ${eventName}`);
+ }
+ return this;
+ },
+
+ // 清空所有事件
+ clear() {
+ this.events = {};
+ console.log('[bus] 清空所有事件');
+ }
+};
+
+// 导出到全局,方便其他模块使用
+window.EventBus = EventBus;
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..e69de29b
diff --git a/module-lifecycle.js b/module-lifecycle.js
new file mode 100644
index 00000000..6d351209
--- /dev/null
+++ b/module-lifecycle.js
@@ -0,0 +1,90 @@
+// module-lifecycle.js
+// 模块生命周期管理
+
+const ModuleLifecycle = {
+ // 当前激活的模块
+ currentModule: null,
+
+ // 加载模块
+ async load(moduleName, containerId) {
+ console.log(`[lifecycle] 开始加载模块: ${moduleName}`);
+
+ // 如果有当前模块,先卸载
+ if (this.currentModule) {
+ await this.unload(this.currentModule.name);
+ }
+
+ const container = document.getElementById(containerId);
+ if (!container) {
+ console.error(`[lifecycle] 容器不存在: ${containerId}`);
+ return;
+ }
+
+ try {
+ // 假设模块的 HTML 放在 mock-modules/ 下
+ const resp = await fetch(`mock-modules/${moduleName}.html`);
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
+ const html = await resp.text();
+ container.innerHTML = html;
+
+ // 记录当前模块
+ this.currentModule = {
+ name: moduleName,
+ containerId: containerId,
+ loadedAt: new Date()
+ };
+
+ // 触发模块的 onLoad 钩子(如果定义了)
+ if (window[`onModuleLoad_${moduleName}`]) {
+ window[`onModuleLoad_${moduleName}`]();
+ }
+
+ console.log(`[lifecycle] 模块加载完成: ${moduleName}`);
+ return this.currentModule;
+ } catch (err) {
+ console.error(`[lifecycle] 加载模块失败 ${moduleName}:`, err);
+ container.innerHTML = `加载模块 ${moduleName} 失败
`;
+ }
+ },
+
+ // 卸载当前模块
+ async unload(moduleName) {
+ if (!this.currentModule || this.currentModule.name !== moduleName) {
+ console.log(`[lifecycle] 模块 ${moduleName} 未激活,无需卸载`);
+ return;
+ }
+
+ console.log(`[lifecycle] 开始卸载模块: ${moduleName}`);
+
+ // 触发模块的 onUnload 钩子(如果定义了)
+ if (window[`onModuleUnload_${moduleName}`]) {
+ window[`onModuleUnload_${moduleName}`]();
+ }
+
+ // 取消该模块订阅的所有事件(约定:模块的事件名前缀为模块名)
+ // 这里简单演示,实际可能需要更精细的管理
+ Object.keys(EventBus.events).forEach(eventName => {
+ if (eventName.startsWith(moduleName)) {
+ EventBus.off(eventName);
+ }
+ });
+
+ // 清空容器
+ const container = document.getElementById(this.currentModule.containerId);
+ if (container) {
+ container.innerHTML = '';
+ }
+
+ this.currentModule = null;
+ console.log(`[lifecycle] 模块卸载完成: ${moduleName}`);
+ },
+
+ // 发送消息给指定模块(通过事件总线)
+ sendMessage(targetModule, eventName, data) {
+ const fullEventName = `${targetModule}:${eventName}`;
+ EventBus.emit(fullEventName, data);
+ console.log(`[lifecycle] 发送消息给 ${targetModule}: ${eventName}`, data);
+ }
+};
+
+window.ModuleLifecycle = ModuleLifecycle;
diff --git a/modules/m-channel/adapters/m06-adapter.js b/modules/m-channel/adapters/m06-adapter.js
new file mode 100644
index 00000000..4651cb12
--- /dev/null
+++ b/modules/m-channel/adapters/m06-adapter.js
@@ -0,0 +1,61 @@
+// M06 工单管理模块适配器
+window.M06Adapter = {
+ moduleId: 'm06',
+ moduleName: '工单管理',
+
+ // 初始化模块(由 ModuleAdapter 调用)
+ init: function(containerId) {
+ console.log('[M06Adapter] 初始化');
+
+ // 通过核心适配器加载
+ if (window.ModuleAdapter) {
+ return ModuleAdapter.loadModule(this.moduleId, containerId);
+ } else {
+ console.error('[M06Adapter] ModuleAdapter 未加载');
+ return null;
+ }
+ },
+
+ // 发送消息到工单模块
+ sendMessage: function(type, payload) {
+ return ModuleAdapter.sendMessage(this.moduleId, { type, payload });
+ },
+
+ // 监听工单模块事件
+ onTicketCreate: function(callback) {
+ EventBus.on('module:m06:message', function(data) {
+ if (data.type === 'ticket:create') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onTicketUpdate: function(callback) {
+ EventBus.on('module:m06:message', function(data) {
+ if (data.type === 'ticket:update') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onTicketDelete: function(callback) {
+ EventBus.on('module:m06:message', function(data) {
+ if (data.type === 'ticket:delete') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ // 销毁模块
+ destroy: function() {
+ ModuleAdapter.unloadModule(this.moduleId);
+ }
+};
+
+// 注册到模块注册表
+if (window.ModuleRegistry) {
+ ModuleRegistry.register('m06', window.M06Adapter);
+ console.log('[M06Adapter] 已注册到模块注册表');
+}
+
+console.log('[M06Adapter] 已加载');
diff --git a/modules/m-channel/adapters/m08-adapter.js b/modules/m-channel/adapters/m08-adapter.js
new file mode 100644
index 00000000..b549438a
--- /dev/null
+++ b/modules/m-channel/adapters/m08-adapter.js
@@ -0,0 +1,47 @@
+// M08 数据统计模块适配器
+window.M08Adapter = {
+ moduleId: 'm08',
+ moduleName: '数据统计',
+
+ init: function(containerId) {
+ console.log('[M08Adapter] 初始化');
+ if (window.ModuleAdapter) {
+ return ModuleAdapter.loadModule(this.moduleId, containerId);
+ } else {
+ console.error('[M08Adapter] ModuleAdapter 未加载');
+ return null;
+ }
+ },
+
+ sendMessage: function(type, payload) {
+ return ModuleAdapter.sendMessage(this.moduleId, { type, payload });
+ },
+
+ // 监听统计模块事件
+ onStatsRefresh: function(callback) {
+ EventBus.on('module:m08:message', function(data) {
+ if (data.type === 'stats:refresh') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onStatsExport: function(callback) {
+ EventBus.on('module:m08:message', function(data) {
+ if (data.type === 'stats:export') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ destroy: function() {
+ ModuleAdapter.unloadModule(this.moduleId);
+ }
+};
+
+if (window.ModuleRegistry) {
+ ModuleRegistry.register('m08', window.M08Adapter);
+ console.log('[M08Adapter] 已注册到模块注册表');
+}
+
+console.log('[M08Adapter] 已加载');
diff --git a/modules/m-channel/adapters/m11-adapter.js b/modules/m-channel/adapters/m11-adapter.js
new file mode 100644
index 00000000..4e3a7b25
--- /dev/null
+++ b/modules/m-channel/adapters/m11-adapter.js
@@ -0,0 +1,47 @@
+// M11 系统组件库适配器
+window.M11Adapter = {
+ moduleId: 'm11',
+ moduleName: '组件库',
+
+ init: function(containerId) {
+ console.log('[M11Adapter] 初始化');
+ if (window.ModuleAdapter) {
+ return ModuleAdapter.loadModule(this.moduleId, containerId);
+ } else {
+ console.error('[M11Adapter] ModuleAdapter 未加载');
+ return null;
+ }
+ },
+
+ sendMessage: function(type, payload) {
+ return ModuleAdapter.sendMessage(this.moduleId, { type, payload });
+ },
+
+ // 监听组件库事件
+ onThemeChange: function(callback) {
+ EventBus.on('module:m11:message', function(data) {
+ if (data.type === 'theme:change') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onComponentSelect: function(callback) {
+ EventBus.on('module:m11:message', function(data) {
+ if (data.type === 'component:select') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ destroy: function() {
+ ModuleAdapter.unloadModule(this.moduleId);
+ }
+};
+
+if (window.ModuleRegistry) {
+ ModuleRegistry.register('m11', window.M11Adapter);
+ console.log('[M11Adapter] 已注册到模块注册表');
+}
+
+console.log('[M11Adapter] 已加载');
diff --git a/modules/m-channel/adapters/module-adapter.js b/modules/m-channel/adapters/module-adapter.js
new file mode 100644
index 00000000..26841ce9
--- /dev/null
+++ b/modules/m-channel/adapters/module-adapter.js
@@ -0,0 +1,140 @@
+// 模块适配器核心 - 万能转接头
+window.ModuleAdapter = {
+ // 已加载的真实模块缓存
+ loadedModules: {},
+
+ // 适配器配置
+ config: {
+ m06: {
+ name: '工单管理',
+ path: '/m06-ticket/index.html', // 真实路径,但文件可能不全
+ width: '100%',
+ height: '500px',
+ events: ['ticket:create', 'ticket:update', 'ticket:delete']
+ },
+ m08: {
+ name: '数据统计',
+ path: '/modules/m08/index.html', // 暂时未知,先保留错误路径触发边界
+ width: '100%',
+ height: '500px',
+ events: ['stats:refresh', 'stats:export']
+ },
+ m11: {
+ name: '组件库',
+ path: '/m11-module/index.html', // 正确路径!
+ width: '100%',
+ height: '600px',
+ events: ['theme:change', 'component:select']
+ }
+ },
+
+ // 加载真实模块(通过 iframe 沙箱)
+ loadModule: function(moduleId, containerId) {
+ console.log(`[adapter] 加载真实模块: ${moduleId}`);
+
+ const container = document.getElementById(containerId);
+ if (!container) {
+ console.error(`[adapter] 容器 ${containerId} 不存在`);
+ return null;
+ }
+
+ // 清理容器
+ container.innerHTML = '';
+
+ const config = this.config[moduleId];
+ if (!config) {
+ console.error(`[adapter] 未知模块: ${moduleId}`);
+ container.innerHTML = `模块配置不存在
`;
+ return null;
+ }
+
+ // 创建 iframe 沙箱(玻璃展柜)
+ const iframe = document.createElement('iframe');
+ iframe.src = config.path;
+ iframe.style.width = config.width;
+ iframe.style.height = config.height;
+ iframe.style.border = 'none';
+ iframe.style.borderRadius = '8px';
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
+ iframe.setAttribute('allow', 'clipboard-read; clipboard-write');
+
+ // 添加加载指示器
+ const loadingDiv = document.createElement('div');
+ loadingDiv.className = 'module-loading';
+ loadingDiv.textContent = `加载 ${config.name}...`;
+ container.appendChild(loadingDiv);
+
+ // iframe 加载完成后移除加载指示器
+ iframe.onload = () => {
+ loadingDiv.remove();
+ console.log(`[adapter] ${moduleId} 加载完成`);
+
+ // 建立跨框架通信桥梁
+ this.setupMessageBridge(iframe, moduleId);
+ };
+
+ iframe.onerror = () => {
+ loadingDiv.remove();
+ container.innerHTML = `模块加载失败,请重试
`;
+ };
+
+ container.appendChild(iframe);
+ this.loadedModules[moduleId] = { iframe, containerId };
+
+ return iframe;
+ },
+
+ // 重新加载模块(错误重试)
+ reloadModule: function(moduleId, containerId) {
+ console.log(`[adapter] 重新加载模块: ${moduleId}`);
+ this.loadModule(moduleId, containerId);
+ },
+
+ // 建立消息桥梁(iframe 和主页通信)
+ setupMessageBridge: function(iframe, moduleId) {
+ // 监听来自 iframe 的消息
+ window.addEventListener('message', function(event) {
+ // 安全检查:确保消息来自正确的 iframe
+ if (event.source !== iframe.contentWindow) return;
+
+ const data = event.data;
+ if (!data || !data.type) return;
+
+ console.log(`[adapter] 收到来自 ${moduleId} 的消息:`, data);
+
+ // 转发为事件总线消息
+ if (window.EventBus) {
+ EventBus.emit(`module:${moduleId}:message`, {
+ from: moduleId,
+ type: data.type,
+ payload: data.payload
+ });
+ }
+ });
+ },
+
+ // 向模块发送消息
+ sendMessage: function(moduleId, message) {
+ const module = this.loadedModules[moduleId];
+ if (!module || !module.iframe) {
+ console.error(`[adapter] 模块 ${moduleId} 未加载`);
+ return false;
+ }
+
+ module.iframe.contentWindow.postMessage(message, '*');
+ return true;
+ },
+
+ // 卸载模块
+ unloadModule: function(moduleId) {
+ const module = this.loadedModules[moduleId];
+ if (module) {
+ const container = document.getElementById(module.containerId);
+ if (container) container.innerHTML = '';
+ delete this.loadedModules[moduleId];
+ console.log(`[adapter] 卸载模块: ${moduleId}`);
+ }
+ }
+};
+
+console.log('[adapter] 模块适配器已加载');
diff --git a/modules/m-channel/app.js b/modules/m-channel/app.js
index 486b8f3d..152d4aa9 100644
--- a/modules/m-channel/app.js
+++ b/modules/m-channel/app.js
@@ -1,45 +1,50 @@
-// 应用入口:初始化路由和模块加载器的协作
+// 应用入口
+console.log('[app] 启动中...');
-// 当路由变化时,如果离开频道页,自动卸载模块
-window.addEventListener('hashchange', () => {
- const path = window.location.hash.slice(2) || 'home';
- if (path !== 'channel') {
- // 离开频道页时卸载模块(避免残留)
- if (window.unloadModule) window.unloadModule();
+// 等待 DOM 加载完成
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('[app] DOM 已加载');
+
+ const contentEl = document.getElementById('channel-content');
+ if (!contentEl) {
+ console.error('[app] 找不到 #channel-content');
+ return;
}
-});
-
-// 页面加载完成后,如果当前是频道页,绑定卡片事件
-document.addEventListener('DOMContentLoaded', () => {
- // 延迟一点等待视图渲染
- setTimeout(initChannelPage, 100);
-});
-
-// 每次视图加载完成后也可能触发(路由引擎加载视图后)
-// 所以我们用 MutationObserver 监听 router-view 的变化,当内容变成频道页时初始化
-const observer = new MutationObserver(() => {
- const routerView = document.getElementById('router-view');
- if (!routerView) return;
- // 检查是否包含 .channel-view
- if (routerView.querySelector('.channel-view')) {
- initChannelPage();
+
+ // 初始化路由器
+ if (window.ChannelRouter) {
+ ChannelRouter.init(contentEl);
}
-});
-observer.observe(document.getElementById('router-view'), { childList: true, subtree: true });
-
-function initChannelPage() {
- const cards = document.querySelectorAll('.module-card');
- cards.forEach(card => {
- card.removeEventListener('click', cardClickHandler); // 防止重复绑定
- card.addEventListener('click', cardClickHandler);
+
+ // 绑定导航按钮
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ btn.addEventListener('click', function() {
+ const channel = this.dataset.channel;
+ if (channel && window.ChannelRouter) {
+ ChannelRouter.navigateTo(channel);
+
+ // 标记已访问
+ this.classList.add('visited');
+ ChannelState.markVisited(channel);
+ }
+ });
});
-}
+
+ console.log('[app] 初始化完成');
+});
-function cardClickHandler(e) {
- const card = e.currentTarget;
- const moduleId = card.dataset.module;
- if (moduleId && window.loadModule) {
- // 可选:先卸载再加载(但 loadModule 会覆盖,所以不需要显式 unload)
- window.loadModule(moduleId);
- }
-}
+// 拦截所有事件总线消息,用于调试面板
+const originalEmit = EventBus.emit;
+EventBus.emit = function(event, data) {
+ // 保存到调试日志
+ if (!window.debugMessages) window.debugMessages = [];
+ window.debugMessages.push({
+ event,
+ data,
+ time: new Date().toLocaleTimeString()
+ });
+
+ // 调用原始方法
+ originalEmit.call(this, event, data);
+};
+console.log('[app] 事件总线拦截器已安装');
diff --git a/modules/m-channel/backup-混乱版/app.js b/modules/m-channel/backup-混乱版/app.js
new file mode 100644
index 00000000..25e29f0a
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/app.js
@@ -0,0 +1,42 @@
+// app.js - 光湖频道动态渲染引擎入口
+console.log('[app] 启动中...');
+
+// 等待 DOM 完全加载
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('[app] DOM 已加载,初始化组件...');
+
+ // 获取内容容器
+ const contentEl = document.getElementById('channel-content');
+ if (!contentEl) {
+ console.error('[app] 找不到 #channel-content 元素');
+ return;
+ }
+
+ // 恢复状态(如果 ChannelState 存在)
+ if (window.ChannelState) {
+ const savedState = ChannelState.restoreState();
+ console.log('[app] 恢复的状态:', savedState);
+ } else {
+ console.warn('[app] ChannelState 未加载');
+ }
+
+ // 初始化路由器
+ if (window.ChannelRouter) {
+ ChannelRouter.init(contentEl);
+ } else {
+ console.error('[app] ChannelRouter 未加载');
+ return;
+ }
+
+ // 绑定导航按钮点击事件
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ btn.addEventListener('click', function(e) {
+ const channel = this.dataset.channel;
+ if (channel) {
+ ChannelRouter.navigateTo(channel);
+ }
+ });
+ });
+
+ console.log('[app] 初始化完成');
+});
diff --git a/modules/m-channel/backup-混乱版/channel-router-backup.js b/modules/m-channel/backup-混乱版/channel-router-backup.js
new file mode 100644
index 00000000..614fc3ed
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/channel-router-backup.js
@@ -0,0 +1,98 @@
+// ================== 路由配置 ==================
+const routes = {
+ 'home': 'views/home.html',
+ 'channel': 'views/channel.html',
+ 'about': 'views/about.html'
+};
+
+// 获取当前 hash 中的路径(去掉 #/)
+function getHashPath() {
+ const hash = window.location.hash.slice(1) || '/';
+ const path = hash.startsWith('/') ? hash.slice(1) : hash;
+ return path || 'home';
+}
+
+// ================== 加载视图 ==================
+async function loadView(path) {
+ const routerView = document.getElementById('router-view');
+ if (!routerView) return;
+
+ // 显示加载动画
+ routerView.innerHTML = '';
+
+ try {
+ const viewFile = routes[path];
+ if (!viewFile) {
+ await load404(routerView);
+ return;
+ }
+
+ const response = await fetch(viewFile);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ const html = await response.text();
+ routerView.innerHTML = html;
+ } catch (error) {
+ console.error('加载视图失败:', error);
+ routerView.innerHTML = `
+
+ ❌ 加载失败:${error.message}
+ 请检查文件是否存在,或刷新重试
+
+ `;
+ }
+
+ // 更新导航高亮和状态栏
+ updateActiveNav(path);
+ updateStatusBar(path);
+}
+
+// 加载 404 页面
+async function load404(container) {
+ try {
+ const resp = await fetch('views/404.html');
+ if (resp.ok) {
+ container.innerHTML = await resp.text();
+ } else {
+ container.innerHTML = '⚠️ 404 - 页面未找到
';
+ }
+ } catch {
+ container.innerHTML = '⚠️ 404 - 页面未找到
';
+ }
+}
+
+// 更新导航高亮
+function updateActiveNav(path) {
+ document.querySelectorAll('.nav-link').forEach(link => {
+ link.classList.remove('active');
+ const linkPath = link.getAttribute('href').slice(2);
+ if (linkPath === path) {
+ link.classList.add('active');
+ }
+ });
+}
+
+// 更新状态栏
+function updateStatusBar(path) {
+ const statusEl = document.getElementById('current-route');
+ if (statusEl) {
+ statusEl.textContent = `当前路由:/${path}`;
+ }
+}
+
+// 监听 hash 变化
+window.addEventListener('hashchange', () => {
+ const path = getHashPath();
+ loadView(path);
+});
+
+// 首次加载
+window.addEventListener('DOMContentLoaded', () => {
+ if (!window.location.hash) {
+ window.location.hash = '#/home';
+ } else {
+ const path = getHashPath();
+ loadView(path);
+ }
+});
diff --git a/modules/m-channel/backup-混乱版/channel-router.js b/modules/m-channel/backup-混乱版/channel-router.js
new file mode 100644
index 00000000..b1930c6d
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/channel-router.js
@@ -0,0 +1,267 @@
+/**
+ * channel-router.js
+ * 用户频道路由引擎 - 带过渡动画和状态持久化
+ * 集成ChannelState和动画控制
+ */
+
+// 依赖全局变量:ChannelState 需要先加载
+(function(global) {
+ 'use strict';
+
+ // 默认配置
+ const defaultConfig = {
+ containerSelector: '#channel-content',
+ routes: {},
+ defaultRoute: '/home',
+ mode: 'fade', // 'fade' 或 'slide'
+ transitionDuration: 300
+ };
+
+ class ChannelRouter {
+ constructor(config) {
+ this.config = Object.assign({}, defaultConfig, config);
+ this.container = document.querySelector(this.config.containerSelector);
+ if (!this.container) {
+ throw new Error(`Container ${this.config.containerSelector} not found`);
+ }
+
+ // 确保容器有相对定位
+ this.container.style.position = 'relative';
+ this.container.classList.add('route-container');
+
+ // 当前活动路由
+ this.currentRoute = null;
+ this.currentPageElement = null;
+
+ // 防抖定时器
+ this.navTimer = null;
+
+ // 绑定方法
+ this.navigate = this.navigate.bind(this);
+ this.goBack = this.goBack.bind(this);
+ this.goForward = this.goForward.bind(this);
+ this.setMode = this.setMode.bind(this);
+ this.handlePopState = this.handlePopState.bind(this);
+
+ // 初始化模式
+ this.container.classList.add(`${this.config.mode}-mode`);
+
+ // 从ChannelState恢复上次的路由
+ this.initFromState();
+
+ // 监听浏览器前进后退
+ window.addEventListener('popstate', this.handlePopState);
+
+ console.log('[router] 初始化完成,模式:', this.config.mode);
+ }
+
+ // 从ChannelState恢复
+ initFromState() {
+ if (global.ChannelState) {
+ const state = global.ChannelState.getState();
+ const targetRoute = state.currentRoute || this.config.defaultRoute;
+ // 不触发pushState,直接渲染
+ this.renderRoute(targetRoute, { replace: true, fromState: true });
+ console.log('[router] 从状态恢复路由:', targetRoute);
+ } else {
+ // 没有状态管理器,走默认
+ this.renderRoute(this.config.defaultRoute, { replace: true });
+ }
+ }
+
+ // 渲染路由(内部方法)
+ renderRoute(path, options = {}) {
+ const { replace = false, fromState = false } = options;
+ const route = this.config.routes[path];
+ if (!route) {
+ console.warn(`[router] 路由 ${path} 未定义,使用404`);
+ // 可以跳转到404页面,这里简单返回
+ return;
+ }
+
+ // 如果是相同路由且不是强制刷新,不重复渲染
+ if (this.currentRoute === path && !options.force) {
+ return;
+ }
+
+ // 创建新页面元素
+ const newPage = document.createElement('div');
+ newPage.className = `route-page ${this.config.mode === 'slide' ? 'slide-enter' : ''}`;
+ newPage.innerHTML = route.template || route.content || '';
+
+ // 如果有模块加载器,执行模块加载
+ if (global.ModuleLoader && route.module) {
+ // 这里简化,实际可能需要加载模块
+ console.log('[router] 加载模块:', route.module);
+ }
+
+ // 旧页面元素
+ const oldPage = this.currentPageElement;
+
+ // 设置新页面为激活状态
+ newPage.classList.add('active');
+
+ // 如果是滑入模式,根据方向添加额外类
+ if (this.config.mode === 'slide') {
+ // 通过history判断方向:前进/后退
+ const direction = this.getDirection(path);
+ if (direction === 'back') {
+ this.container.classList.add('backward');
+ this.container.classList.remove('forward');
+ } else {
+ this.container.classList.add('forward');
+ this.container.classList.remove('backward');
+ }
+ }
+
+ // 添加新页面到容器
+ this.container.appendChild(newPage);
+
+ // 触发重绘以确保动画
+ newPage.offsetHeight;
+
+ // 如果有旧页面,移除它的active类并添加退出动画类
+ if (oldPage) {
+ oldPage.classList.remove('active');
+ if (this.config.mode === 'slide') {
+ oldPage.classList.add('slide-exit');
+ }
+ }
+
+ // 动画结束后清理旧页面
+ const onTransitionEnd = (e) => {
+ if (e.target === newPage || e.target === oldPage) {
+ if (oldPage && oldPage.parentNode) {
+ oldPage.parentNode.removeChild(oldPage);
+ }
+ newPage.removeEventListener('transitionend', onTransitionEnd);
+ }
+ };
+ newPage.addEventListener('transitionend', onTransitionEnd);
+
+ // 更新当前路由
+ this.currentRoute = path;
+ this.currentPageElement = newPage;
+
+ // 更新导航菜单激活状态
+ this.updateActiveNav(path);
+
+ // 保存状态(如果不是从状态恢复来的)
+ if (!fromState && global.ChannelState) {
+ // 判断是push还是replace
+ if (replace) {
+ // 替换当前历史记录(不增加新记录)
+ // 状态管理需要相应处理:替换当前记录而不是push
+ global.ChannelState.setCurrentRoute(path);
+ // 同时替换浏览器历史
+ if (!options.skipHistory) {
+ history.replaceState({ route: path }, '', `#${path}`);
+ }
+ } else {
+ // 正常跳转,push到历史
+ global.ChannelState.pushHistory(path);
+ if (!options.skipHistory) {
+ history.pushState({ route: path }, '', `#${path}`);
+ }
+ }
+ } else if (!global.ChannelState) {
+ // 没有状态管理器,只处理浏览器历史
+ if (!replace && !options.skipHistory) {
+ history.pushState({ route: path }, '', `#${path}`);
+ } else if (replace && !options.skipHistory) {
+ history.replaceState({ route: path }, '', `#${path}`);
+ }
+ }
+
+ console.log(`[router] 导航到 ${path}${replace ? ' (replace)' : ''}`);
+ }
+
+ // 判断前进后退方向(简单实现:看是否在历史栈中)
+ getDirection(path) {
+ if (!global.ChannelState) return 'forward';
+ const state = global.ChannelState.getState();
+ const currentIndex = state.historyIndex;
+ const stack = state.historyStack;
+ // 如果path在历史栈中且在当前位置之后,是后退?需要更精确
+ // 这里简化:根据当前路由和目标的索引比较
+ if (this.currentRoute) {
+ const currentIdx = stack.indexOf(this.currentRoute);
+ const targetIdx = stack.indexOf(path);
+ if (targetIdx < currentIdx) return 'back';
+ }
+ return 'forward';
+ }
+
+ // 更新导航菜单激活样式
+ updateActiveNav(path) {
+ document.querySelectorAll('.channel-nav a').forEach(link => {
+ const href = link.getAttribute('href').replace('#', '');
+ if (href === path) {
+ link.classList.add('active');
+ } else {
+ link.classList.remove('active');
+ }
+ });
+ }
+
+ // 公开导航方法
+ navigate(path, options = {}) {
+ // 防抖处理
+ if (this.navTimer) clearTimeout(this.navTimer);
+ this.navTimer = setTimeout(() => {
+ this.renderRoute(path, options);
+ this.navTimer = null;
+ }, 10); // 微小延迟确保快速点击不重叠
+ }
+
+ // 后退
+ goBack() {
+ if (global.ChannelState) {
+ const prev = global.ChannelState.goBack();
+ if (prev) {
+ this.renderRoute(prev, { fromState: true, skipHistory: true });
+ } else {
+ console.log('[router] 已在最前');
+ }
+ } else {
+ history.back();
+ }
+ }
+
+ // 前进
+ goForward() {
+ if (global.ChannelState) {
+ const next = global.ChannelState.goForward();
+ if (next) {
+ this.renderRoute(next, { fromState: true, skipHistory: true });
+ } else {
+ console.log('[router] 已在最后');
+ }
+ } else {
+ history.forward();
+ }
+ }
+
+ // 处理popstate事件(浏览器前进后退)
+ handlePopState(event) {
+ const route = event.state?.route || this.config.defaultRoute;
+ if (global.ChannelState) {
+ // 从状态中恢复索引,但不需要重复push
+ this.renderRoute(route, { fromState: true, skipHistory: true });
+ } else {
+ this.renderRoute(route, { skipHistory: true });
+ }
+ }
+
+ // 切换动画模式
+ setMode(mode) {
+ if (mode !== 'fade' && mode !== 'slide') return;
+ this.container.classList.remove('fade-mode', 'slide-mode');
+ this.container.classList.add(`${mode}-mode`);
+ this.config.mode = mode;
+ console.log('[router] 切换动画模式为:', mode);
+ }
+ }
+
+ global.ChannelRouter = ChannelRouter;
+})(window);
diff --git a/modules/m-channel/backup-混乱版/channel-state.js b/modules/m-channel/backup-混乱版/channel-state.js
new file mode 100644
index 00000000..35ea1734
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/channel-state.js
@@ -0,0 +1,149 @@
+/**
+ * channel-state.js
+ * 频道状态管理器 - 记忆妈妈的浏览足迹
+ * 使用localStorage持久化,刷新页面自动恢复
+ * 功能:保存/恢复当前路由、已访问模块列表、历史栈
+ */
+
+const ChannelState = (function() {
+ const STORAGE_KEY = 'm-channel-state';
+
+ // 默认状态
+ const defaultState = {
+ currentRoute: '/home',
+ visitedModules: [], // 已访问过的模块ID列表
+ historyStack: ['/home'], // 历史记录栈
+ historyIndex: 0 // 当前在历史栈中的位置
+ };
+
+ let state = { ...defaultState };
+
+ // 加载本地存储的状态
+ function load() {
+ try {
+ const saved = localStorage.getItem(STORAGE_KEY);
+ if (saved) {
+ state = JSON.parse(saved);
+ // 确保必要字段存在
+ if (!state.visitedModules) state.visitedModules = [];
+ if (!state.historyStack || !Array.isArray(state.historyStack)) {
+ state.historyStack = [state.currentRoute || '/home'];
+ }
+ if (typeof state.historyIndex !== 'number') {
+ state.historyIndex = 0;
+ }
+ console.log('[state] restore', state);
+ } else {
+ reset();
+ }
+ } catch (e) {
+ console.warn('[state] load failed, use default', e);
+ reset();
+ }
+ return state;
+ }
+
+ // 保存当前状态到localStorage
+ function save() {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+ console.log('[state] save', state);
+ } catch (e) {
+ console.warn('[state] save failed', e);
+ }
+ }
+
+ // 重置为默认状态
+ function reset() {
+ state = { ...defaultState };
+ save();
+ }
+
+ // 更新当前路由
+ function setCurrentRoute(route) {
+ if (state.currentRoute !== route) {
+ state.currentRoute = route;
+ // 添加到已访问模块(如果是模块路由)
+ if (route.startsWith('/module/')) {
+ const moduleId = route.replace('/module/', '');
+ if (!state.visitedModules.includes(moduleId)) {
+ state.visitedModules.push(moduleId);
+ }
+ }
+ save();
+ }
+ }
+
+ // 添加到历史栈(用于前进后退)
+ function pushHistory(route) {
+ // 如果当前不在栈顶,先截断后面的记录
+ if (state.historyIndex < state.historyStack.length - 1) {
+ state.historyStack = state.historyStack.slice(0, state.historyIndex + 1);
+ }
+ state.historyStack.push(route);
+ state.historyIndex = state.historyStack.length - 1;
+ setCurrentRoute(route); // 会触发保存
+ }
+
+ // 后退
+ function goBack() {
+ if (state.historyIndex > 0) {
+ state.historyIndex--;
+ state.currentRoute = state.historyStack[state.historyIndex];
+ save();
+ return state.currentRoute;
+ }
+ return null;
+ }
+
+ // 前进
+ function goForward() {
+ if (state.historyIndex < state.historyStack.length - 1) {
+ state.historyIndex++;
+ state.currentRoute = state.historyStack[state.historyIndex];
+ save();
+ return state.currentRoute;
+ }
+ return null;
+ }
+
+ // 获取当前状态
+ function getState() {
+ return { ...state };
+ }
+
+ // 标记模块为已访问(外部调用)
+ function markModuleVisited(moduleId) {
+ if (!state.visitedModules.includes(moduleId)) {
+ state.visitedModules.push(moduleId);
+ save();
+ }
+ }
+
+ // 清除状态(用于测试)
+ function clear() {
+ localStorage.removeItem(STORAGE_KEY);
+ reset();
+ }
+
+ // 初始化:加载状态
+ load();
+
+ return {
+ load,
+ save,
+ reset,
+ setCurrentRoute,
+ pushHistory,
+ goBack,
+ goForward,
+ getState,
+ markModuleVisited,
+ clear
+ };
+})();
+
+// 导出(如果是模块环境)
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = ChannelState;
+}
diff --git a/modules/m-channel/backup-混乱版/channel-style.css b/modules/m-channel/backup-混乱版/channel-style.css
new file mode 100644
index 00000000..f2a8cd20
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/channel-style.css
@@ -0,0 +1,132 @@
+/**
+ * channel-style.css
+ * 频道基础样式 + 过渡动画变量
+ */
+
+/* 引入过渡动画 */
+@import url('channel-transition.css');
+
+:root {
+ --primary-color: #4a90e2;
+ --secondary-color: #f5f5f5;
+ --text-color: #333;
+ --border-radius: 8px;
+ --transition-duration: 0.3s;
+ --transition-timing: ease;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ margin: 0;
+ padding: 20px;
+ background: #f0f2f5;
+ color: var(--text-color);
+}
+
+/* 频道头部 */
+.channel-header {
+ background: white;
+ padding: 15px 20px;
+ border-radius: var(--border-radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ margin-bottom: 20px;
+}
+
+.channel-header h1 {
+ margin: 0;
+ font-size: 1.8rem;
+ color: var(--primary-color);
+}
+
+/* 导航菜单 */
+.channel-nav {
+ background: white;
+ padding: 10px 20px;
+ border-radius: var(--border-radius);
+ margin-bottom: 20px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.channel-nav ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ gap: 20px;
+}
+
+.channel-nav a {
+ text-decoration: none;
+ color: var(--text-color);
+ padding: 8px 16px;
+ border-radius: 20px;
+ transition: background 0.2s;
+}
+
+.channel-nav a:hover {
+ background: var(--secondary-color);
+}
+
+.channel-nav a.active {
+ background: var(--primary-color);
+ color: white;
+}
+
+/* 内容区域 */
+.channel-content {
+ background: white;
+ padding: 20px;
+ border-radius: var(--border-radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ min-height: 300px;
+}
+
+/* 已访问模块标记 */
+.module-link.visited {
+ position: relative;
+}
+
+.module-link.visited::after {
+ content: "✓";
+ position: absolute;
+ top: -5px;
+ right: -5px;
+ background: var(--primary-color);
+ color: white;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* 动画模式切换按钮 */
+.animation-toggle {
+ margin-left: auto;
+ display: flex;
+ gap: 10px;
+}
+
+.animation-toggle button {
+ padding: 5px 15px;
+ border: 1px solid #ddd;
+ background: white;
+ border-radius: 20px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.animation-toggle button.active {
+ background: var(--primary-color);
+ color: white;
+ border-color: var(--primary-color);
+}
+
+/* 调试面板样式(后续使用) */
+.debug-panel {
+ margin-top: 30px;
+ border-top: 2px dashed #ccc;
+ padding-top: 20px;
+}
diff --git a/modules/m-channel/backup-混乱版/channel-transition.css b/modules/m-channel/backup-混乱版/channel-transition.css
new file mode 100644
index 00000000..b13bd025
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/channel-transition.css
@@ -0,0 +1,83 @@
+/**
+ * channel-transition.css
+ * 路由过渡动画 - 让切换像呼吸一样自然
+ * 提供两种模式:淡入淡出(fade) / 滑入滑出(slide)
+ */
+
+/* 基础容器样式 */
+.route-container {
+ position: relative;
+ width: 100%;
+ min-height: 200px;
+}
+
+/* 所有路由页面默认绝对定位,便于重叠动画 */
+.route-page {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ transition: opacity 0.3s ease, transform 0.3s ease;
+}
+
+/* 淡入淡出模式 */
+.fade-mode .route-page {
+ opacity: 0;
+ transform: scale(0.98);
+ pointer-events: none;
+}
+
+.fade-mode .route-page.active {
+ opacity: 1;
+ transform: scale(1);
+ pointer-events: auto;
+ position: relative; /* 激活页变为相对定位,占据文档流高度 */
+}
+
+/* 滑入滑出模式 */
+.slide-mode .route-page {
+ opacity: 1;
+ transform: translateX(0);
+ pointer-events: none;
+}
+
+.slide-mode .route-page.active {
+ position: relative;
+ pointer-events: auto;
+}
+
+/* 进入动画:从右侧滑入 */
+.slide-mode .route-page.slide-enter {
+ transform: translateX(100%);
+}
+
+.slide-mode .route-page.active.slide-enter {
+ transform: translateX(0);
+}
+
+/* 离开动画:向左侧滑出(用于后退时的反向) */
+.slide-mode .route-page.slide-exit {
+ transform: translateX(-100%);
+}
+
+/* 前进/后退方向控制 */
+.slide-mode.forward .route-page.slide-enter {
+ transform: translateX(100%);
+}
+
+.slide-mode.forward .route-page.active.slide-enter {
+ transform: translateX(0);
+}
+
+.slide-mode.backward .route-page.slide-enter {
+ transform: translateX(-100%);
+}
+
+.slide-mode.backward .route-page.active.slide-enter {
+ transform: translateX(0);
+}
+
+/* 防止快速点击时动画重叠 */
+.route-page {
+ will-change: transform, opacity;
+}
diff --git a/modules/m-channel/backup-混乱版/event-bus.js b/modules/m-channel/backup-混乱版/event-bus.js
new file mode 100644
index 00000000..ab14007c
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/event-bus.js
@@ -0,0 +1,111 @@
+/**
+ * event-bus.js
+ * 事件总线 - 模块间的群聊频道
+ * 发布/订阅模式,支持命名空间和调试日志
+ */
+
+const EventBus = (function() {
+ // 存储订阅者:{ eventName: [handler1, handler2, ...] }
+ const listeners = {};
+ // 调试模式开关
+ let debugMode = true;
+
+ // 订阅事件
+ function on(eventName, handler) {
+ if (typeof handler !== 'function') {
+ console.error('[bus] 订阅必须提供函数');
+ return;
+ }
+
+ if (!listeners[eventName]) {
+ listeners[eventName] = [];
+ }
+ listeners[eventName].push(handler);
+
+ if (debugMode) {
+ console.log(`[bus] 订阅事件: ${eventName},当前订阅数: ${listeners[eventName].length}`);
+ }
+
+ // 返回取消订阅函数
+ return function off() {
+ off(eventName, handler);
+ };
+ }
+
+ // 取消订阅
+ function off(eventName, handler) {
+ if (!listeners[eventName]) return;
+
+ if (handler) {
+ // 移除特定handler
+ const index = listeners[eventName].indexOf(handler);
+ if (index !== -1) {
+ listeners[eventName].splice(index, 1);
+ if (debugMode) {
+ console.log(`[bus] 取消订阅: ${eventName},剩余: ${listeners[eventName].length}`);
+ }
+ }
+ } else {
+ // 移除该事件所有订阅
+ delete listeners[eventName];
+ if (debugMode) {
+ console.log(`[bus] 移除所有订阅: ${eventName}`);
+ }
+ }
+ }
+
+ // 触发事件
+ function emit(eventName, data) {
+ if (!listeners[eventName]) {
+ if (debugMode) {
+ console.log(`[bus] 触发事件 ${eventName} 但无订阅者`);
+ }
+ return;
+ }
+
+ if (debugMode) {
+ console.log(`[bus] 触发事件: ${eventName},数据:`, data);
+ }
+
+ // 复制一份以防在遍历过程中修改
+ const handlers = listeners[eventName].slice();
+ handlers.forEach(handler => {
+ try {
+ handler(data, eventName);
+ } catch (e) {
+ console.error(`[bus] 事件 ${eventName} 处理出错:`, e);
+ }
+ });
+ }
+
+ // 清空所有订阅
+ function clear() {
+ for (let key in listeners) {
+ delete listeners[key];
+ }
+ if (debugMode) {
+ console.log('[bus] 清空所有订阅');
+ }
+ }
+
+ // 开启/关闭调试
+ function setDebug(enable) {
+ debugMode = enable;
+ }
+
+ return {
+ on,
+ off,
+ emit,
+ clear,
+ setDebug
+ };
+})();
+
+// 挂载到全局
+window.EventBus = EventBus;
+
+// 如果是模块环境
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = EventBus;
+}
diff --git a/modules/m-channel/backup-混乱版/index.html b/modules/m-channel/backup-混乱版/index.html
new file mode 100644
index 00000000..e86e9904
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ 光湖频道 · 动态渲染引擎
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/m-channel/backup-混乱版/module-lifecycle.js b/modules/m-channel/backup-混乱版/module-lifecycle.js
new file mode 100644
index 00000000..37daed60
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/module-lifecycle.js
@@ -0,0 +1,90 @@
+/**
+ * module-lifecycle.js
+ * 模块生命周期管理 - 让模块知道自己何时加载/卸载/收到消息
+ * 配合事件总线使用
+ */
+
+const ModuleLifecycle = (function() {
+ // 存储每个模块的钩子函数
+ const hooks = {};
+
+ // 注册模块的生命周期钩子
+ function register(moduleId, lifecycles) {
+ if (!moduleId) return;
+
+ hooks[moduleId] = {
+ onLoad: lifecycles.onLoad || null,
+ onUnload: lifecycles.onUnload || null,
+ onMessage: lifecycles.onMessage || null
+ };
+
+ console.log(`[lifecycle] 注册模块: ${moduleId}`, lifecycles);
+
+ // 如果已经加载(比如页面初始化时),自动触发onLoad?
+ // 这里留给loader去调用
+ }
+
+ // 触发模块加载
+ function triggerLoad(moduleId, params) {
+ const moduleHooks = hooks[moduleId];
+ if (moduleHooks && moduleHooks.onLoad) {
+ try {
+ moduleHooks.onLoad(params);
+ console.log(`[lifecycle] onLoad 模块: ${moduleId}`);
+ } catch (e) {
+ console.error(`[lifecycle] onLoad 模块 ${moduleId} 出错:`, e);
+ }
+ }
+ }
+
+ // 触发模块卸载
+ function triggerUnload(moduleId) {
+ const moduleHooks = hooks[moduleId];
+ if (moduleHooks && moduleHooks.onUnload) {
+ try {
+ moduleHooks.onUnload();
+ console.log(`[lifecycle] onUnload 模块: ${moduleId}`);
+ } catch (e) {
+ console.error(`[lifecycle] onUnload 模块 ${moduleId} 出错:`, e);
+ }
+ }
+
+ // 卸载时自动取消该模块的所有事件订阅
+ // 这里简单使用事件总线的off,但需要知道该模块订阅了哪些事件
+ // 我们约定模块在订阅时使用带命名空间的事件名,比如 moduleA:click
+ // 或者在onUnload里手动取消。为了简化,我们不清除订阅,但可以在onUnload里做。
+ // 更完善的做法是记录每个模块的订阅列表,但这里先不实现。
+ }
+
+ // 触发模块收到消息(由事件总线转发时调用)
+ function triggerMessage(moduleId, message, data) {
+ const moduleHooks = hooks[moduleId];
+ if (moduleHooks && moduleHooks.onMessage) {
+ try {
+ moduleHooks.onMessage(message, data);
+ console.log(`[lifecycle] onMessage 模块: ${moduleId} 消息: ${message}`);
+ } catch (e) {
+ console.error(`[lifecycle] onMessage 模块 ${moduleId} 出错:`, e);
+ }
+ }
+ }
+
+ // 获取模块的钩子(用于调试)
+ function getHooks(moduleId) {
+ return hooks[moduleId] || null;
+ }
+
+ return {
+ register,
+ triggerLoad,
+ triggerUnload,
+ triggerMessage,
+ getHooks
+ };
+})();
+
+window.ModuleLifecycle = ModuleLifecycle;
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = ModuleLifecycle;
+}
diff --git a/modules/m-channel/backup-混乱版/module-loader.js b/modules/m-channel/backup-混乱版/module-loader.js
new file mode 100644
index 00000000..27474424
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/module-loader.js
@@ -0,0 +1,116 @@
+/**
+ * module-loader.js
+ * 模块加载器 - 负责动态加载模块HTML/JS,并管理生命周期
+ * 集成事件总线和生命周期钩子
+ */
+
+const ModuleLoader = (function() {
+ // 已加载的模块缓存
+ const loadedModules = {};
+
+ // 加载模块
+ async function loadModule(moduleId, container, params = {}) {
+ if (loadedModules[moduleId]) {
+ console.log(`[loader] 模块 ${moduleId} 已加载,直接显示`);
+ showModule(moduleId, container, params);
+ return;
+ }
+
+ try {
+ console.log(`[loader] 正在加载模块: ${moduleId}`);
+
+ // 获取模块URL(这里使用mock-modules下的html文件)
+ const url = `mock-modules/mock-${moduleId}.html`;
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ const html = await response.text();
+
+ // 解析HTML,提取body内容作为模块内容
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+ let moduleContent = doc.body.innerHTML;
+
+ // 提取script标签并执行
+ const scripts = doc.querySelectorAll('script');
+ scripts.forEach(script => {
+ const newScript = document.createElement('script');
+ if (script.src) {
+ newScript.src = script.src;
+ } else {
+ newScript.textContent = script.textContent;
+ }
+ document.body.appendChild(newScript);
+ // 注意:动态添加的脚本会立即执行
+ });
+
+ // 存储模块内容
+ loadedModules[moduleId] = {
+ content: moduleContent,
+ scripts: scripts.length
+ };
+
+ // 显示模块
+ showModule(moduleId, container, params);
+
+ // 触发生命周期 onLoad
+ ModuleLifecycle.triggerLoad(moduleId, params);
+
+ // 标记已访问(状态管理)
+ if (window.ChannelState) {
+ ChannelState.markModuleVisited(moduleId);
+ }
+
+ console.log(`[loader] 模块 ${moduleId} 加载完成`);
+ } catch (error) {
+ console.error(`[loader] 加载模块 ${moduleId} 失败:`, error);
+ container.innerHTML = `加载失败:${error.message}
`;
+ }
+ }
+
+ // 显示已加载的模块
+ function showModule(moduleId, container, params) {
+ const mod = loadedModules[moduleId];
+ if (!mod) return;
+
+ container.innerHTML = mod.content;
+
+ // 如果有参数,可以通过自定义事件传递
+ if (params) {
+ const event = new CustomEvent('module:params', { detail: params });
+ container.dispatchEvent(event);
+ }
+
+ // 通知模块已显示(通过生命周期?)
+ // 可以用triggerMessage
+ ModuleLifecycle.triggerMessage(moduleId, 'show', params);
+ }
+
+ // 卸载模块
+ function unloadModule(moduleId) {
+ if (loadedModules[moduleId]) {
+ // 触发生命周期 onUnload
+ ModuleLifecycle.triggerUnload(moduleId);
+
+ // 清理缓存(可选)
+ delete loadedModules[moduleId];
+ console.log(`[loader] 模块 ${moduleId} 已卸载`);
+ }
+ }
+
+ // 预加载模块
+ function preloadModule(moduleId) {
+ // 简单fetch但不显示
+ fetch(`mock-modules/mock-${moduleId}.html`).catch(() => {});
+ }
+
+ return {
+ loadModule,
+ unloadModule,
+ preloadModule
+ };
+})();
+
+window.ModuleLoader = ModuleLoader;
diff --git a/modules/m-channel/backup-混乱版/module-registry.js b/modules/m-channel/backup-混乱版/module-registry.js
new file mode 100644
index 00000000..a0a87ff3
--- /dev/null
+++ b/modules/m-channel/backup-混乱版/module-registry.js
@@ -0,0 +1,7 @@
+const moduleRegistry = {
+ 'mock-a': 'mock-modules/mock-a.html',
+ 'mock-b': 'mock-modules/mock-b.html',
+ 'mock-c': 'mock-modules/mock-c.html',
+ 'mock-d': 'mock-modules/mock-d.html'
+};
+window.moduleRegistry = moduleRegistry;
diff --git a/modules/m-channel/channel-analytics.js b/modules/m-channel/channel-analytics.js
new file mode 100644
index 00000000..f1b43148
--- /dev/null
+++ b/modules/m-channel/channel-analytics.js
@@ -0,0 +1,163 @@
+
+/**
+ * channel-analytics.js
+ * 频道数据采集核心·环节8
+ * 记录模块访问次数、停留时间、页面加载速度
+ */
+
+const ChannelAnalytics = (function() {
+ const STORAGE_KEY = 'channel-analytics-data';
+
+ // 数据结构初始化
+ function getDefaultData() {
+ return {
+ modules: {}, // {moduleId: {visits: 0, totalDuration: 0, loadTimes: [], dailyVisits: {} }}
+ globalDaily: {}, // {'YYYY-MM-DD': totalVisits}
+ lastUpdated: null
+ };
+ }
+
+ // localStorage 读写
+ function loadData() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) return JSON.parse(raw);
+ } catch(e) {
+ console.log('⚠️ 读取分析数据失败,重新初始化');
+ }
+ return getDefaultData();
+ }
+
+ function saveData(data) {
+ data.lastUpdated = new Date().toISOString();
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+ }
+
+ // 日期工具
+ function today() {
+ return new Date().toISOString().split('T')[0];
+ }
+
+ // 确保模块数据结构存在
+ function ensureModule(data, moduleId) {
+ if (!data.modules[moduleId]) {
+ data.modules[moduleId] = {
+ visits: 0,
+ totalDuration: 0,
+ loadTimes: [],
+ dailyVisits: {}
+ };
+ }
+ return data.modules[moduleId];
+ }
+
+ // 当前会话状态
+ let currentModule = null;
+ let enterTime = null;
+ let loadStartTime = null;
+
+ // 公开方法
+ return {
+ // 记录模块访问
+ recordVisit: function(moduleId) {
+ if (!moduleId) return;
+ const data = loadData();
+ const mod = ensureModule(data, moduleId);
+
+ // 访问计数 +1
+ mod.visits++;
+
+ // 每日访问计数
+ const d = today();
+ mod.dailyVisits[d] = (mod.dailyVisits[d] || 0) + 1;
+
+ // 全局每日访问
+ data.globalDaily[d] = (data.globalDaily[d] || 0) + 1;
+
+ saveData(data);
+ console.log('📊 已记录:模块 ' + moduleId + ' 被访问,累计 ' + mod.visits + ' 次');
+
+ // 记录进入时间
+ this.startSession(moduleId);
+ },
+
+ // 开始计时
+ startSession: function(moduleId) {
+ // 先结束上一个模块的计时
+ if (currentModule && enterTime) {
+ this.endSession();
+ }
+ currentModule = moduleId;
+ enterTime = performance.now();
+ loadStartTime = performance.now();
+ },
+
+ // 结束计时(切换模块或离开时调用)
+ endSession: function() {
+ if (!currentModule || !enterTime) return;
+ const duration = Math.round((performance.now() - enterTime) / 1000); // 秒
+ const data = loadData();
+ const mod = ensureModule(data, currentModule);
+ mod.totalDuration += duration;
+ saveData(data);
+ console.log('⏱️ 停留时间:模块 ' + currentModule + ' 停留约 ' + duration + ' 秒');
+ currentModule = null;
+ enterTime = null;
+ },
+
+ // 记录加载性能
+ recordLoadTime: function(moduleId, loadTimeMs) {
+ if (!moduleId) return;
+ const data = loadData();
+ const mod = ensureModule(data, moduleId);
+ mod.loadTimes.push(loadTimeMs);
+ // 只保留最近50次
+ if (mod.loadTimes.length > 50) {
+ mod.loadTimes = mod.loadTimes.slice(-50);
+ }
+ saveData(data);
+ console.log('⚡ 加载耗时:模块 ' + moduleId + ' 加载 ' + Math.round(loadTimeMs) + ' 毫秒');
+ },
+
+ // 标记加载开始
+ markLoadStart: function() {
+ loadStartTime = performance.now();
+ },
+
+ // 标记加载完成并记录
+ markLoadEnd: function(moduleId) {
+ if (loadStartTime && moduleId) {
+ const loadTime = performance.now() - loadStartTime;
+ this.recordLoadTime(moduleId, loadTime);
+ loadStartTime = null;
+ }
+ },
+
+ // 获取所有数据(面板用)
+ getAllData: function() {
+ return loadData();
+ },
+
+ // 获取最近7天趋势
+ getWeeklyTrend: function() {
+ const data = loadData();
+ const trend = [];
+ for (let i = 6; i >= 0; i--) {
+ const d = new Date();
+ d.setDate(d.getDate() - i);
+ const dateStr = d.toISOString().split('T')[0];
+ trend.push({
+ date: dateStr,
+ visits: data.globalDaily[dateStr] || 0
+ });
+ }
+ return trend;
+ },
+
+ // 清除所有数据(调试用)
+ clearAll: function() {
+ localStorage.removeItem(STORAGE_KEY);
+ console.log('🗑️ 所有分析数据已清除');
+ }
+ };
+})();
diff --git a/modules/m-channel/channel-complete.html b/modules/m-channel/channel-complete.html
new file mode 100644
index 00000000..91629573
--- /dev/null
+++ b/modules/m-channel/channel-complete.html
@@ -0,0 +1,267 @@
+
+
+
+
+
+ 光湖频道 · 完全版
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
主题色
+
+
+
+
+
+
+
+
+
+
统计数据
+
+
+
+
其他
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/m-channel/channel-dashboard.css b/modules/m-channel/channel-dashboard.css
new file mode 100644
index 00000000..1269c86f
--- /dev/null
+++ b/modules/m-channel/channel-dashboard.css
@@ -0,0 +1,199 @@
+/**
+ * channel-dashboard.css
+ * 频道数据面板样式·深色主题
+ */
+
+.dashboard-container {
+ padding: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+ color: #e0e0e0;
+}
+
+.dashboard-header {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.dashboard-header h2 {
+ font-size: 24px;
+ color: #4fc3f7;
+ margin-bottom: 8px;
+}
+
+.dashboard-header p {
+ font-size: 14px;
+ color: #888;
+}
+
+/* 图表网格 */
+.charts-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.chart-card {
+ background: #1a1a2e;
+ border-radius: 12px;
+ padding: 20px;
+ border: 1px solid #2a2a4a;
+}
+
+.chart-card h3 {
+ font-size: 16px;
+ color: #4fc3f7;
+ margin-bottom: 15px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #2a2a4a;
+}
+
+.chart-card canvas {
+ width: 100% !important;
+ height: 200px !important;
+ display: block;
+}
+
+/* 柱状图 */
+.bar-chart {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-around;
+ height: 200px;
+ padding: 10px 0;
+ border-bottom: 2px solid #333;
+}
+
+.bar-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ max-width: 80px;
+}
+
+.bar-fill {
+ width: 40px;
+ border-radius: 4px 4px 0 0;
+ transition: height 0.5s ease;
+ min-height: 4px;
+}
+
+.bar-label {
+ margin-top: 8px;
+ font-size: 11px;
+ color: #aaa;
+ text-align: center;
+ word-break: break-all;
+}
+
+.bar-value {
+ font-size: 12px;
+ color: #fff;
+ margin-bottom: 4px;
+}
+
+/* 饼图 */
+.pie-container {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+}
+
+.pie-canvas-wrap {
+ flex-shrink: 0;
+}
+
+.pie-legend {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.pie-legend li {
+ display: flex;
+ align-items: center;
+ margin-bottom: 6px;
+ font-size: 13px;
+}
+
+.pie-legend .dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ margin-right: 8px;
+ flex-shrink: 0;
+}
+
+/* 折线图 */
+.line-chart-wrap {
+ position: relative;
+ height: 200px;
+}
+
+/* 性能表格 */
+.perf-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 13px;
+}
+
+.perf-table th {
+ text-align: left;
+ padding: 8px;
+ background: #2a2a4a;
+ color: #4fc3f7;
+ font-weight: normal;
+}
+
+.perf-table td {
+ padding: 8px;
+ border-bottom: 1px solid #2a2a4a;
+}
+
+.perf-table tr:last-child td {
+ border-bottom: none;
+}
+
+.perf-table .slow {
+ color: #ff5252;
+ font-weight: bold;
+}
+
+/* 按钮 */
+.dashboard-actions {
+ margin-top: 20px;
+ text-align: right;
+}
+
+.btn-clear {
+ background: #ff5252;
+ color: #fff;
+ border: none;
+ padding: 10px 24px;
+ border-radius: 8px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: background 0.3s;
+}
+
+.btn-clear:hover {
+ background: #ff1744;
+}
+
+/* 响应式 */
+@media (max-width: 768px) {
+ .charts-grid {
+ grid-template-columns: 1fr;
+ }
+ .pie-container {
+ flex-direction: column;
+ }
+}
+
+/* 饼图 canvas 保持正方形 */
+.pie-canvas-wrap canvas {
+ width: 160px !important;
+ height: 160px !important;
+}
diff --git a/modules/m-channel/channel-dashboard.js b/modules/m-channel/channel-dashboard.js
new file mode 100644
index 00000000..13f3555b
--- /dev/null
+++ b/modules/m-channel/channel-dashboard.js
@@ -0,0 +1,201 @@
+
+/**
+ * channel-dashboard.js
+ * 频道数据面板逻辑·图表渲染
+ */
+
+const ChannelDashboard = (function() {
+ // 配色方案
+ const COLORS = ['#4fc3f7', '#ffb74d', '#ff8a80', '#aed581', '#ba68c8', '#4dd0e1', '#ffd54f'];
+
+ // 柱状图渲染
+ function renderBarChart() {
+ const container = document.getElementById('visitBarChart');
+ if (!container) return;
+ const data = ChannelAnalytics.getAllData();
+ const modules = data.modules;
+ const keys = Object.keys(modules);
+ if (keys.length === 0) {
+ container.innerHTML = '暂无数据,多点几个模块再来看
';
+ return;
+ }
+ const maxVisits = Math.max.apply(null, keys.map(function(k) { return modules[k].visits; })) || 1;
+ let html = '';
+ keys.forEach(function(id, i) {
+ const mod = modules[id];
+ const height = Math.max(4, (mod.visits / maxVisits) * 180);
+ const color = COLORS[i % COLORS.length];
+ html += '' +
+ '
' + mod.visits + '' +
+ '
' +
+ '
' + id.replace('m-', '') + '' +
+ '
';
+ });
+ container.innerHTML = html;
+ }
+
+ // 饼图渲染
+ function renderPieChart() {
+ const canvas = document.getElementById('pieCanvas');
+ const legendEl = document.getElementById('pieLegend');
+ if (!canvas || !legendEl) return;
+ const ctx = canvas.getContext('2d');
+ const data = ChannelAnalytics.getAllData();
+ const modules = data.modules;
+ const keys = Object.keys(modules);
+ const total = keys.reduce(function(sum, k) {
+ return sum + (modules[k].totalDuration || 0);
+ }, 0);
+
+ // 清空画布
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ if (total === 0 || keys.length === 0) {
+ ctx.fillStyle = '#333';
+ ctx.beginPath();
+ ctx.arc(80, 80, 70, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = '#666';
+ ctx.font = '12px sans-serif';
+ ctx.textAlign = 'center';
+ ctx.fillText('暂无数据', 80, 84);
+ legendEl.innerHTML = '';
+ return;
+ }
+
+ let startAngle = -Math.PI / 2;
+ let legendHtml = '';
+ keys.forEach(function(id, i) {
+ const mod = modules[id];
+ const pct = mod.totalDuration / total;
+ const sweep = pct * Math.PI * 2;
+ const color = COLORS[i % COLORS.length];
+
+ ctx.beginPath();
+ ctx.moveTo(80, 80);
+ ctx.arc(80, 80, 70, startAngle, startAngle + sweep);
+ ctx.closePath();
+ ctx.fillStyle = color;
+ ctx.fill();
+
+ startAngle += sweep;
+
+ const minutes = Math.round(mod.totalDuration / 60);
+ const pctStr = Math.round(pct * 100);
+ legendHtml += '' +
+ id.replace('m-', '') + ' ' + pctStr + '% (' + minutes + '分钟)';
+ });
+ legendEl.innerHTML = legendHtml;
+ }
+
+ // 折线图渲染
+ function renderLineChart() {
+ const canvas = document.getElementById('lineCanvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ const trend = ChannelAnalytics.getWeeklyTrend();
+ const w = canvas.width;
+ const h = canvas.height;
+
+ ctx.clearRect(0, 0, w, h);
+
+ const maxVal = Math.max.apply(null, trend.map(function(t) { return t.visits; })) || 1;
+ const padLeft = 40;
+ const padBottom = 30;
+ const padTop = 10;
+ const chartW = w - padLeft - 20;
+ const chartH = h - padBottom - padTop;
+ const stepX = chartW / (trend.length - 1 || 1);
+
+ // 网格线
+ ctx.strokeStyle = '#2a2a4a';
+ ctx.lineWidth = 1;
+ for (let g = 0; g <= 4; g++) {
+ let gy = padTop + (chartH / 4) * g;
+ ctx.beginPath();
+ ctx.moveTo(padLeft, gy);
+ ctx.lineTo(w - 20, gy);
+ ctx.stroke();
+ }
+
+ // 折线
+ ctx.strokeStyle = '#4fc3f7';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ trend.forEach(function(t, i) {
+ let x = padLeft + stepX * i;
+ let y = padTop + chartH - (t.visits / maxVal) * chartH;
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ });
+ ctx.stroke();
+
+ // 数据点
+ trend.forEach(function(t, i) {
+ let x = padLeft + stepX * i;
+ let y = padTop + chartH - (t.visits / maxVal) * chartH;
+ ctx.beginPath();
+ ctx.arc(x, y, 4, 0, Math.PI * 2);
+ ctx.fillStyle = '#4fc3f7';
+ ctx.fill();
+ });
+
+ // 标签
+ ctx.fillStyle = '#aaa';
+ ctx.font = '11px sans-serif';
+ ctx.textAlign = 'center';
+ trend.forEach(function(t, i) {
+ let x = padLeft + stepX * i;
+ let y = padTop + chartH + 15;
+ ctx.fillText(t.date.slice(5), x, y);
+ });
+ }
+
+ // 性能表格渲染
+ function renderPerfTable() {
+ const tbody = document.getElementById('perfTableBody');
+ if (!tbody) return;
+ const data = ChannelAnalytics.getAllData();
+ const modules = data.modules;
+ const keys = Object.keys(modules);
+ if (keys.length === 0) {
+ tbody.innerHTML = '| 暂无数据 |
';
+ return;
+ }
+
+ let html = '';
+ keys.forEach(function(id) {
+ const mod = modules[id];
+ const avgLoad = mod.loadTimes.length ?
+ Math.round(mod.loadTimes.reduce((a, b) => a + b, 0) / mod.loadTimes.length) : 0;
+ const minutes = Math.round(mod.totalDuration / 60);
+ const statusClass = avgLoad > 300 ? 'slow' : '';
+ const statusText = avgLoad > 300 ? '⚠️ 慢' : '✅ 正常';
+
+ html += '' +
+ '| ' + id.replace('m-', '') + ' | ' +
+ '' + mod.visits + ' | ' +
+ '' + minutes + '分钟 | ' +
+ '' + avgLoad + 'ms | ' +
+ '' + statusText + ' | ' +
+ '
';
+ });
+ tbody.innerHTML = html;
+ }
+
+ // 公开方法
+ return {
+ render: function() {
+ renderBarChart();
+ renderPieChart();
+ renderLineChart();
+ renderPerfTable();
+ },
+ clearData: function() {
+ if (confirm('确定清除所有统计数据吗?')) {
+ ChannelAnalytics.clearAll();
+ this.render();
+ console.log('🗑️ 数据已清除,图表已重置');
+ }
+ }
+ };
+})();
diff --git a/modules/m-channel/channel-enhancements.js b/modules/m-channel/channel-enhancements.js
new file mode 100644
index 00000000..36896616
--- /dev/null
+++ b/modules/m-channel/channel-enhancements.js
@@ -0,0 +1,338 @@
+// channel-enhancements.js - 频道搜索、面包屑、快捷键增强功能(终极修复版)
+(function() {
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+
+ function init() {
+ addStyles();
+ createSearchBox();
+ createBreadcrumb();
+ createShortcutHint();
+ initShortcuts();
+ bindCardClicks(); // ★ 新增:为卡片绑定点击切换模块
+ window.addEventListener('hashchange', updateBreadcrumb);
+ updateBreadcrumb();
+ }
+
+ function addStyles() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .channel-search-container {
+ padding: 16px 20px;
+ background: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+ .channel-search-input {
+ flex: 1;
+ padding: 10px 16px;
+ border: 1px solid #ced4da;
+ border-radius: 24px;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.2s;
+ }
+ .channel-search-input:focus {
+ border-color: #4d6bfe;
+ box-shadow: 0 0 0 3px rgba(77,107,254,0.1);
+ }
+ .channel-search-clear {
+ background: none;
+ border: none;
+ font-size: 18px;
+ cursor: pointer;
+ color: #6c757d;
+ padding: 0 8px;
+ display: none;
+ }
+ .channel-search-clear:hover {
+ color: #212529;
+ }
+ .channel-search-input:not(:placeholder-shown) + .channel-search-clear {
+ display: inline-block;
+ }
+ .highlight {
+ background-color: #ffeb3b;
+ padding: 2px 0;
+ border-radius: 2px;
+ }
+ .no-results {
+ text-align: center;
+ padding: 40px;
+ color: #6c757d;
+ font-style: italic;
+ }
+ .channel-breadcrumb {
+ padding: 12px 20px;
+ background: white;
+ border-bottom: 1px solid #e9ecef;
+ font-size: 14px;
+ }
+ .channel-breadcrumb a {
+ color: #4d6bfe;
+ text-decoration: none;
+ }
+ .channel-breadcrumb a:hover {
+ text-decoration: underline;
+ }
+ .channel-breadcrumb span {
+ color: #6c757d;
+ }
+ .channel-breadcrumb .current {
+ color: #212529;
+ font-weight: 500;
+ }
+ .shortcut-hint {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background: rgba(0,0,0,0.7);
+ color: white;
+ padding: 8px 16px;
+ border-radius: 30px;
+ font-size: 12px;
+ backdrop-filter: blur(4px);
+ z-index: 1000;
+ }
+ .shortcut-hint kbd {
+ background: rgba(255,255,255,0.2);
+ padding: 2px 6px;
+ border-radius: 4px;
+ margin: 0 2px;
+ }
+ .module-card.selected {
+ outline: 2px solid #4d6bfe;
+ outline-offset: 2px;
+ transform: scale(1.02);
+ transition: all 0.2s;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ function createSearchBox() {
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.querySelector('.channel-content');
+ if (!container) return;
+
+ const searchContainer = document.createElement('div');
+ searchContainer.className = 'channel-search-container';
+ searchContainer.innerHTML = `
+
+
+ `;
+ container.parentNode.insertBefore(searchContainer, container);
+
+ const searchInput = searchContainer.querySelector('.channel-search-input');
+ const clearBtn = searchContainer.querySelector('.channel-search-clear');
+
+ searchInput.addEventListener('input', function() {
+ filterModules(this.value);
+ });
+
+ clearBtn.addEventListener('click', function() {
+ searchInput.value = '';
+ filterModules('');
+ searchInput.focus();
+ });
+
+ window.__channelSearchInput = searchInput;
+ }
+
+ function filterModules(keyword) {
+ const cards = document.querySelectorAll('.module-card');
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
+ let hasResults = false;
+
+ // 移除所有高亮
+ removeAllHighlights();
+
+ cards.forEach(card => {
+ const text = card.innerText || card.textContent;
+ if (keyword === '') {
+ card.style.display = '';
+ hasResults = true;
+ } else {
+ const lowerText = text.toLowerCase();
+ const lowerKeyword = keyword.toLowerCase();
+ if (lowerText.includes(lowerKeyword)) {
+ card.style.display = '';
+ highlightText(card, keyword);
+ hasResults = true;
+ } else {
+ card.style.display = 'none';
+ }
+ }
+ });
+
+ let noResultsEl = document.querySelector('.no-results');
+ if (!hasResults && keyword !== '') {
+ if (!noResultsEl) {
+ noResultsEl = document.createElement('div');
+ noResultsEl.className = 'no-results';
+ noResultsEl.textContent = '没有找到匹配的模块';
+ container.parentNode.insertBefore(noResultsEl, container.nextSibling);
+ }
+ } else {
+ if (noResultsEl) noResultsEl.remove();
+ }
+ }
+
+ function removeAllHighlights() {
+ document.querySelectorAll('.highlight').forEach(span => {
+ const parent = span.parentNode;
+ parent.replaceChild(document.createTextNode(span.textContent), span);
+ parent.normalize();
+ });
+ }
+
+ function highlightText(card, keyword) {
+ const regex = new RegExp(`(${keyword})`, 'gi');
+ const walk = document.createTreeWalker(card, NodeFilter.SHOW_TEXT, {
+ acceptNode: function(node) {
+ if (node.parentNode.classList && node.parentNode.classList.contains('highlight')) {
+ return NodeFilter.FILTER_REJECT;
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ }, false);
+
+ const textNodes = [];
+ while (walk.nextNode()) textNodes.push(walk.currentNode);
+
+ textNodes.forEach(node => {
+ const text = node.nodeValue;
+ if (regex.test(text)) {
+ const span = document.createElement('span');
+ span.className = 'highlight';
+ span.innerHTML = text.replace(regex, '$1');
+ node.parentNode.replaceChild(span, node);
+ }
+ });
+ }
+
+ function createBreadcrumb() {
+ const searchContainer = document.querySelector('.channel-search-container');
+ const breadcrumb = document.createElement('div');
+ breadcrumb.className = 'channel-breadcrumb';
+ breadcrumb.id = 'channelBreadcrumb';
+ if (searchContainer) {
+ searchContainer.parentNode.insertBefore(breadcrumb, searchContainer.nextSibling);
+ } else {
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
+ if (container) {
+ container.parentNode.insertBefore(breadcrumb, container);
+ }
+ }
+ }
+
+ function updateBreadcrumb() {
+ const breadcrumb = document.getElementById('channelBreadcrumb');
+ if (!breadcrumb) return;
+ const hash = window.location.hash.slice(1) || '';
+ let moduleName = '频道';
+ if (hash.startsWith('module-')) {
+ const moduleId = hash.replace('module-', '');
+ const moduleNames = {
+ 'M06': '工单管理',
+ 'M08': '数据看板',
+ 'M11': '组件库',
+ 'debug': '调试面板'
+ };
+ moduleName = moduleNames[moduleId] || moduleId;
+ }
+ breadcrumb.innerHTML = `
+ 首页 >
+ 频道 >
+ ${moduleName}
+ `;
+ }
+
+ function createShortcutHint() {
+ const hint = document.createElement('div');
+ hint.className = 'shortcut-hint';
+ hint.innerHTML = `
+ ⌘K 搜索 ·
+ ↑↓ 选择 ·
+ Enter 打开 ·
+ Esc 关闭
+ `;
+ document.body.appendChild(hint);
+ }
+
+ function initShortcuts() {
+ document.addEventListener('keydown', function(e) {
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
+ e.preventDefault();
+ const searchInput = window.__channelSearchInput;
+ if (searchInput) searchInput.focus();
+ }
+ if (e.key === 'Escape') {
+ const searchInput = window.__channelSearchInput;
+ if (searchInput && document.activeElement === searchInput) {
+ searchInput.value = '';
+ filterModules('');
+ searchInput.blur();
+ } else if (searchInput && searchInput.value) {
+ searchInput.value = '';
+ filterModules('');
+ }
+ }
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
+ const cards = Array.from(document.querySelectorAll('.module-card:not([style*="display: none"])'));
+ if (cards.length === 0) return;
+ e.preventDefault();
+ let selectedIndex = cards.findIndex(card => card.classList.contains('selected'));
+ if (selectedIndex === -1) {
+ selectedIndex = e.key === 'ArrowDown' ? 0 : cards.length - 1;
+ } else {
+ cards[selectedIndex].classList.remove('selected');
+ if (e.key === 'ArrowDown') {
+ selectedIndex = (selectedIndex + 1) % cards.length;
+ } else {
+ selectedIndex = (selectedIndex - 1 + cards.length) % cards.length;
+ }
+ }
+ cards[selectedIndex].classList.add('selected');
+ cards[selectedIndex].scrollIntoView({ block: 'nearest' });
+ }
+ if (e.key === 'Enter') {
+ const selected = document.querySelector('.module-card.selected');
+ if (selected) {
+ selected.click(); // 触发我们绑定的点击事件
+ }
+ }
+ });
+ }
+
+ // ★★★ 核心修复:为所有卡片绑定点击切换模块 ★★★
+ function bindCardClicks() {
+ const grid = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.body;
+ grid.addEventListener('click', function(e) {
+ const card = e.target.closest('.module-card');
+ if (!card) return;
+
+ // 阻止可能冲突的其他事件(可选)
+ e.preventDefault();
+
+ // 根据卡片文字判断是哪个模块
+ const text = card.innerText || card.textContent;
+ let moduleId = null;
+ if (text.includes('工单管理')) moduleId = 'M06';
+ else if (text.includes('数据统计')) moduleId = 'M08';
+ else if (text.includes('组件库')) moduleId = 'M11';
+ else if (text.includes('调试面板')) moduleId = 'debug';
+ // 如果识别不到,尝试从其他属性获取(比如 data-module-id)
+ else if (card.dataset.moduleId) moduleId = card.dataset.moduleId;
+
+ if (moduleId) {
+ // 改变 hash,触发路由更新
+ window.location.hash = `module-${moduleId}`;
+ }
+ });
+ }
+})();
diff --git a/modules/m-channel/channel-favorites.js b/modules/m-channel/channel-favorites.js
new file mode 100644
index 00000000..4208f653
--- /dev/null
+++ b/modules/m-channel/channel-favorites.js
@@ -0,0 +1,271 @@
+// 频道收藏与拖拽排序管理
+window.ChannelFavorites = {
+ // 当前正在拖拽的元素
+ draggingElement: null,
+
+ // 初始化
+ init: function() {
+ console.log('[favorites] 初始化');
+ this.bindEvents();
+ this.renderFavorites();
+ },
+
+ // 绑定事件
+ bindEvents: function() {
+ // 使用事件委托监听星星点击
+ document.addEventListener('click', (e) => {
+ const star = e.target.closest('.favorite-star');
+ if (star) {
+ e.preventDefault();
+ const card = star.closest('.module-card');
+ if (card) {
+ const moduleId = card.dataset.module;
+ if (moduleId) {
+ this.toggleFavorite(moduleId, star);
+ }
+ }
+ }
+ });
+
+ // 监听偏好变化,重新渲染收藏状态
+ if (window.EventBus) {
+ EventBus.on('preferences:changed', (data) => {
+ if (data.key === 'favorites' || data.full) {
+ this.updateAllStars();
+ }
+ });
+ }
+ },
+
+ // 切换收藏状态
+ toggleFavorite: function(moduleId, starElement) {
+ if (!window.ChannelPreferences) return;
+
+ const isNowFavorite = ChannelPreferences.toggleFavorite(moduleId);
+
+ // 更新星星样式
+ if (starElement) {
+ if (isNowFavorite) {
+ starElement.classList.add('active');
+ starElement.textContent = '★';
+ } else {
+ starElement.classList.remove('active');
+ starElement.textContent = '☆';
+ }
+ }
+
+ // 触发布局更新(收藏置顶)
+ this.updateFavoritesOrder();
+
+ // 发送事件
+ if (window.EventBus) {
+ EventBus.emit('favorite:toggled', {
+ module: moduleId,
+ favorite: isNowFavorite
+ });
+ }
+ },
+
+ // 更新所有星星的显示状态
+ updateAllStars: function() {
+ const favorites = window.ChannelPreferences ? ChannelPreferences.getFavorites() : [];
+ document.querySelectorAll('.favorite-star').forEach(star => {
+ const card = star.closest('.module-card');
+ if (card) {
+ const moduleId = card.dataset.module;
+ if (moduleId) {
+ if (favorites.includes(moduleId)) {
+ star.classList.add('active');
+ star.textContent = '★';
+ } else {
+ star.classList.remove('active');
+ star.textContent = '☆';
+ }
+ }
+ }
+ });
+ },
+
+ // 根据收藏状态重新排序(收藏置顶)
+ updateFavoritesOrder: function() {
+ const container = document.querySelector('.channel-content');
+ if (!container) return;
+
+ const cards = Array.from(container.querySelectorAll('.module-card'));
+ const favorites = window.ChannelPreferences ? ChannelPreferences.getFavorites() : [];
+
+ // 按收藏状态和原有顺序排序
+ cards.sort((a, b) => {
+ const aId = a.dataset.module;
+ const bId = b.dataset.module;
+ const aFav = favorites.includes(aId);
+ const bFav = favorites.includes(bId);
+
+ if (aFav && !bFav) return -1;
+ if (!aFav && bFav) return 1;
+
+ // 如果都是收藏或都不是收藏,保持原有顺序
+ const aOrder = cards.indexOf(a);
+ const bOrder = cards.indexOf(b);
+ return aOrder - bOrder;
+ });
+
+ // 重新插入到容器中
+ cards.forEach(card => container.appendChild(card));
+
+ // 保存排序到偏好设置
+ const moduleOrder = cards.map(card => card.dataset.module);
+ if (window.ChannelPreferences) {
+ ChannelPreferences.setModuleOrder(moduleOrder);
+ }
+ },
+
+ // 渲染收藏状态(初始化时调用)
+ renderFavorites: function() {
+ this.updateAllStars();
+ },
+
+ // ===== 拖拽排序相关 =====
+
+ // 初始化拖拽
+ initDragAndDrop: function() {
+ console.log('[favorites] 初始化拖拽排序');
+
+ const container = document.querySelector('.channel-content');
+ if (!container) return;
+
+ // 为每个卡片添加拖拽手柄
+ container.querySelectorAll('.module-card').forEach(card => {
+ // 检查是否已有拖拽手柄
+ if (!card.querySelector('.drag-handle')) {
+ const header = card.querySelector('.module-card-header');
+ if (header) {
+ const handle = document.createElement('span');
+ handle.className = 'drag-handle';
+ handle.innerHTML = '⋮⋮';
+ handle.setAttribute('draggable', 'false');
+ header.insertBefore(handle, header.firstChild);
+ }
+ }
+
+ // 设置 draggable
+ card.setAttribute('draggable', 'true');
+
+ // 移除旧监听器,添加新监听器
+ card.removeEventListener('dragstart', this.handleDragStart);
+ card.removeEventListener('dragend', this.handleDragEnd);
+ card.removeEventListener('dragover', this.handleDragOver);
+ card.removeEventListener('dragenter', this.handleDragEnter);
+ card.removeEventListener('dragleave', this.handleDragLeave);
+ card.removeEventListener('drop', this.handleDrop);
+
+ card.addEventListener('dragstart', this.handleDragStart.bind(this));
+ card.addEventListener('dragend', this.handleDragEnd.bind(this));
+ card.addEventListener('dragover', this.handleDragOver);
+ card.addEventListener('dragenter', this.handleDragEnter);
+ card.addEventListener('dragleave', this.handleDragLeave);
+ card.addEventListener('drop', this.handleDrop.bind(this));
+ });
+ },
+
+ // 拖拽开始
+ handleDragStart: function(e) {
+ this.draggingElement = e.target.closest('.module-card');
+ if (!this.draggingElement) return;
+
+ e.dataTransfer.setData('text/plain', this.draggingElement.dataset.module);
+ e.dataTransfer.effectAllowed = 'move';
+
+ // 添加拖拽中的样式
+ setTimeout(() => {
+ this.draggingElement.classList.add('dragging');
+ }, 0);
+ },
+
+ // 拖拽结束
+ handleDragEnd: function(e) {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ card.classList.remove('dragging');
+ }
+
+ // 移除所有高亮
+ document.querySelectorAll('.module-card.drop-target').forEach(el => {
+ el.classList.remove('drop-target');
+ });
+
+ this.draggingElement = null;
+ },
+
+ // 拖拽经过(必须阻止默认事件才能成为放置目标)
+ handleDragOver: function(e) {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ },
+
+ // 拖拽进入
+ handleDragEnter: function(e) {
+ const card = e.target.closest('.module-card');
+ if (card && card !== this.draggingElement) {
+ card.classList.add('drop-target');
+ }
+ },
+
+ // 拖拽离开
+ handleDragLeave: function(e) {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ card.classList.remove('drop-target');
+ }
+ },
+
+ // 放置
+ handleDrop: function(e) {
+ e.preventDefault();
+
+ const targetCard = e.target.closest('.module-card');
+ if (!targetCard || !this.draggingElement || targetCard === this.draggingElement) {
+ return;
+ }
+
+ // 移除高亮
+ targetCard.classList.remove('drop-target');
+
+ // 获取所有卡片
+ const container = document.querySelector('.channel-content');
+ const cards = Array.from(container.querySelectorAll('.module-card'));
+
+ const fromIndex = cards.indexOf(this.draggingElement);
+ const toIndex = cards.indexOf(targetCard);
+
+ if (fromIndex === -1 || toIndex === -1) return;
+
+ // 重新排序
+ if (window.ChannelPreferences) {
+ ChannelPreferences.reorderModules(fromIndex, toIndex);
+ }
+
+ // 移动 DOM 元素
+ if (fromIndex < toIndex) {
+ targetCard.insertAdjacentElement('afterend', this.draggingElement);
+ } else {
+ targetCard.insertAdjacentElement('beforebegin', this.draggingElement);
+ }
+
+ // 触发事件
+ if (window.EventBus) {
+ EventBus.emit('favorites:reordered', {
+ from: fromIndex,
+ to: toIndex,
+ module: this.draggingElement.dataset.module
+ });
+ }
+ },
+
+ // 刷新拖拽功能(在布局变化后调用)
+ refreshDragAndDrop: function() {
+ this.initDragAndDrop();
+ }
+};
+
+console.log('[favorites] 已加载');
diff --git a/modules/m-channel/channel-layout.css b/modules/m-channel/channel-layout.css
new file mode 100644
index 00000000..31c39c3f
--- /dev/null
+++ b/modules/m-channel/channel-layout.css
@@ -0,0 +1,145 @@
+/* 频道布局样式 - 三种布局模式 */
+
+/* 基础卡片样式(所有布局共用) */
+.module-card {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ transition: all 0.3s ease;
+ overflow: hidden;
+ position: relative;
+}
+
+.module-card:hover {
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ transform: translateY(-2px);
+}
+
+/* 卡片头部 */
+.module-card-header {
+ padding: 16px;
+ border-bottom: 1px solid #eee;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.module-card-title {
+ font-size: 18px;
+ font-weight: 600;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* 收藏星星 */
+.favorite-star {
+ font-size: 20px;
+ color: #ccc;
+ cursor: pointer;
+ transition: color 0.2s;
+ user-select: none;
+}
+
+.favorite-star.active {
+ color: #fbbf24;
+}
+
+.favorite-star:hover {
+ transform: scale(1.1);
+}
+
+/* 卡片内容区 */
+.module-card-content {
+ padding: 16px;
+ min-height: 100px;
+}
+
+/* 拖拽把手 */
+.drag-handle {
+ font-size: 20px;
+ color: #999;
+ cursor: grab;
+ user-select: none;
+ margin-right: 8px;
+}
+
+.drag-handle:active {
+ cursor: grabbing;
+}
+
+/* ===== 布局1:网格模式(默认) ===== */
+.layout-grid .channel-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 24px;
+ padding: 24px;
+}
+
+.layout-grid .module-card {
+ height: fit-content;
+}
+
+/* ===== 布局2:列表模式(一行一个) ===== */
+.layout-list .channel-content {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 24px;
+}
+
+.layout-list .module-card {
+ width: 100%;
+}
+
+.layout-list .module-card-header {
+ padding: 12px 16px;
+}
+
+.layout-list .module-card-content {
+ padding: 12px 16px;
+}
+
+/* ===== 布局3:紧凑模式(小卡片密排) ===== */
+.layout-compact .channel-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 12px;
+ padding: 16px;
+}
+
+.layout-compact .module-card {
+ font-size: 14px;
+}
+
+.layout-compact .module-card-header {
+ padding: 10px 12px;
+}
+
+.layout-compact .module-card-title {
+ font-size: 16px;
+}
+
+.layout-compact .module-card-content {
+ padding: 10px 12px;
+ min-height: 80px;
+}
+
+/* 布局切换动画 */
+.channel-content {
+ transition: all 0.3s ease-in-out;
+}
+
+/* 拖拽中的样式 */
+.module-card.dragging {
+ opacity: 0.6;
+ transform: scale(0.98);
+ box-shadow: 0 8px 16px rgba(0,0,0,0.2);
+}
+
+/* 拖拽放置区高亮 */
+.module-card.drop-target {
+ border: 2px dashed #3b82f6;
+ background: #f0f9ff;
+}
diff --git a/modules/m-channel/channel-notifications.css b/modules/m-channel/channel-notifications.css
new file mode 100644
index 00000000..5905e8df
--- /dev/null
+++ b/modules/m-channel/channel-notifications.css
@@ -0,0 +1,200 @@
+/* 频道通知系统样式 - 环节7 */
+:root {
+ --update-badge-bg: #2196f3;
+ --unread-dot-bg: #f44336;
+ --notification-panel-bg: #ffffff;
+ --notification-panel-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ --notification-hover-bg: #f5f5f5;
+}
+
+/* 更新标签(蓝色) */
+.update-badge {
+ display: inline-block;
+ background-color: var(--update-badge-bg);
+ color: white;
+ font-size: 12px;
+ font-weight: 500;
+ padding: 2px 8px;
+ border-radius: 12px;
+ margin-top: 8px;
+ align-self: flex-start;
+}
+
+/* 未读小红点(带数字) */
+.unread-dot {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ min-width: 18px;
+ height: 18px;
+ background-color: var(--unread-dot-bg);
+ color: white;
+ font-size: 12px;
+ font-weight: bold;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ z-index: 10;
+}
+
+/* 铃铛图标容器 */
+.bell-container {
+ position: relative;
+ display: inline-block;
+ margin-right: 16px;
+ cursor: pointer;
+}
+
+.bell-icon {
+ font-size: 24px;
+ color: #555;
+ transition: color 0.2s;
+}
+
+.bell-icon:hover {
+ color: #000;
+}
+
+/* 铃铛上的数字气泡 */
+.bell-badge {
+ position: absolute;
+ top: -4px;
+ right: -6px;
+ min-width: 18px;
+ height: 18px;
+ background-color: var(--unread-dot-bg);
+ color: white;
+ font-size: 12px;
+ font-weight: bold;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 4px;
+ z-index: 20;
+}
+
+/* 侧滑通知面板 */
+.notification-panel {
+ position: fixed;
+ top: 0;
+ right: -400px;
+ width: 360px;
+ height: 100vh;
+ background: var(--notification-panel-bg);
+ box-shadow: var(--notification-panel-shadow);
+ transition: right 0.3s ease;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ border-left: 1px solid #e0e0e0;
+}
+
+.notification-panel.open {
+ right: 0;
+}
+
+.panel-header {
+ padding: 20px;
+ border-bottom: 1px solid #e0e0e0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 600;
+}
+
+.panel-header button {
+ background: none;
+ border: none;
+ color: #2196f3;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.panel-header button:hover {
+ text-decoration: underline;
+}
+
+.notification-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+.notification-item {
+ padding: 16px 20px;
+ border-bottom: 1px solid #f0f0f0;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.notification-item:hover {
+ background: var(--notification-hover-bg);
+}
+
+.notification-item.read {
+ opacity: 0.6;
+}
+
+.notification-time {
+ font-size: 12px;
+ color: #999;
+ margin-bottom: 4px;
+}
+
+.notification-module {
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.notification-summary {
+ font-size: 14px;
+ color: #333;
+}
+
+.empty-state {
+ padding: 40px 20px;
+ text-align: center;
+ color: #999;
+ font-size: 14px;
+}
+
+/* 遮罩层 */
+.panel-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.3);
+ z-index: 999;
+ display: none;
+}
+
+.panel-overlay.show {
+ display: block;
+}
+
+/* 让模块卡片成为相对定位容器(用于小红点定位) */
+.module-card {
+ position: relative !important;
+}
+
+/* 强制修复高亮点击问题 */
+.search-highlight,
+.highlight,
+[class*="highlight"] {
+ pointer-events: none !important;
+}
+
+/* 确保卡片本身可点击 */
+.module-card,
+[class*="module-card"] {
+ cursor: pointer;
+ pointer-events: auto !important;
+}
diff --git a/modules/m-channel/channel-notifications.js b/modules/m-channel/channel-notifications.js
new file mode 100644
index 00000000..a14c7818
--- /dev/null
+++ b/modules/m-channel/channel-notifications.js
@@ -0,0 +1,365 @@
+/**
+ * 频道通知系统 - 环节7 (晨星陪伴版)
+ * 功能:模块更新提醒、未读小红点、通知面板、高亮点击修复
+ * 修改:通过卡片文字内容匹配模块ID(解决moduleId undefined问题)
+ */
+
+(function() {
+ // ========== 初始化模拟数据 ==========
+ const STORAGE_KEY = 'channel_notifications';
+
+ // 默认模拟数据(基于已完成模块 M06, M08, M11, 以及当前频道模块)
+ const DEFAULT_UPDATES = {
+ 'M06': {
+ hasUpdate: true,
+ count: 2,
+ updates: [
+ { time: '10:30', summary: '修复了拖拽排序的bug' },
+ { time: '昨天', summary: '新增统计面板' }
+ ]
+ },
+ 'M08': {
+ hasUpdate: true,
+ count: 1,
+ updates: [
+ { time: '昨天', summary: '优化了模块加载性能' }
+ ]
+ },
+ 'M11': {
+ hasUpdate: true,
+ count: 3,
+ updates: [
+ { time: '15:20', summary: '新增键盘快捷键' },
+ { time: '昨天', summary: '修复焦点管理' },
+ { time: '3月10日', summary: '模块生命周期完善' }
+ ]
+ },
+ 'channel': {
+ hasUpdate: true,
+ count: 1,
+ updates: [
+ { time: '现在', summary: '通知系统上线啦!' }
+ ]
+ }
+ };
+
+ // 加载或初始化数据
+ let notificationData = JSON.parse(localStorage.getItem(STORAGE_KEY));
+ if (!notificationData) {
+ notificationData = DEFAULT_UPDATES;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notificationData));
+ }
+
+ // ========== 工具函数:从卡片文字猜测模块ID ==========
+ function guessModuleIdFromCard(card) {
+ // 尝试找卡片里的标题文字
+ const titleElem = card.querySelector('h3, .module-title, .card-title, .module-name');
+ if (titleElem) {
+ const text = titleElem.textContent.trim();
+ // 简单映射:如果文字包含“工单” -> M06
+ if (text.includes('工单')) return 'M06';
+ if (text.includes('数据统计')) return 'M08';
+ if (text.includes('组件库')) return 'M11';
+ if (text.includes('调试面板')) return 'M11'; // 暂时用 M11
+ }
+
+ // 后备:用卡片内所有文字尝试匹配
+ const fullText = card.textContent;
+ if (fullText.includes('工单')) return 'M06';
+ if (fullText.includes('数据统计')) return 'M08';
+ if (fullText.includes('组件库')) return 'M11';
+
+ return null; // 实在猜不到就返回 null
+ }
+
+ function getModuleCards() {
+ return document.querySelectorAll('.module-card');
+ }
+
+ // ========== 更新标签渲染(带日志) ==========
+ function renderUpdateBadges() {
+ console.log('🟦 renderUpdateBadges 开始执行');
+ const cards = document.querySelectorAll('.module-card');
+ console.log('找到卡片数量:', cards.length);
+
+ cards.forEach(card => {
+ // 先尝试原有方法,失败则用猜测
+ let moduleId = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!moduleId) {
+ moduleId = guessModuleIdFromCard(card);
+ }
+ console.log('卡片最终 moduleId:', moduleId);
+
+ // 移除旧的标签
+ const oldBadge = card.querySelector('.update-badge');
+ if (oldBadge) oldBadge.remove();
+
+ const modData = moduleId ? notificationData[moduleId] : null;
+ console.log('模块数据:', moduleId, modData);
+
+ if (modData && modData.hasUpdate) {
+ console.log('✅ 应该添加标签的模块:', moduleId);
+ const badge = document.createElement('span');
+ badge.className = 'update-badge';
+ badge.textContent = '有更新';
+ // 内联样式保证可见
+ badge.style.backgroundColor = '#2196f3';
+ badge.style.color = 'white';
+ badge.style.padding = '2px 8px';
+ badge.style.borderRadius = '12px';
+ badge.style.fontSize = '12px';
+ badge.style.display = 'inline-block';
+ badge.style.marginTop = '8px';
+ // 直接追加到卡片末尾
+ card.appendChild(badge);
+ console.log('✅ 标签已追加到卡片', moduleId);
+ } else {
+ console.log('❌ 不需要添加标签的模块:', moduleId);
+ }
+ });
+ console.log('🟦 renderUpdateBadges 执行完毕');
+ }
+
+ // ========== 小红点渲染 ==========
+ function renderUnreadDots() {
+ const cards = getModuleCards();
+ cards.forEach(card => {
+ let moduleId = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!moduleId) {
+ moduleId = guessModuleIdFromCard(card);
+ }
+ if (!moduleId) return;
+
+ const oldDot = card.querySelector('.unread-dot');
+ if (oldDot) oldDot.remove();
+
+ const modData = notificationData[moduleId];
+ if (modData && modData.hasUpdate && modData.count > 0) {
+ const dot = document.createElement('span');
+ dot.className = 'unread-dot';
+ dot.textContent = modData.count > 9 ? '9+' : modData.count;
+ card.appendChild(dot);
+ }
+ });
+ }
+
+ // ========== 标记模块已读 ==========
+ function markModuleAsRead(moduleId) {
+ if (notificationData[moduleId]) {
+ notificationData[moduleId].hasUpdate = false;
+ notificationData[moduleId].count = 0;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notificationData));
+ renderUpdateBadges();
+ renderUnreadDots();
+ updateBellBadge();
+ renderNotificationPanel();
+ }
+ }
+
+ // ========== 铃铛相关 ==========
+ let bellContainer, panel, overlay;
+ let isPanelOpen = false;
+
+ function createBell() {
+ const header = document.querySelector('.channel-header') || document.querySelector('header') || document.body;
+ bellContainer = document.createElement('div');
+ bellContainer.className = 'bell-container';
+ bellContainer.innerHTML = `
+ 🔔
+ 0
+ `;
+ const title = header.querySelector('h1, h2');
+ if (title) {
+ title.insertAdjacentElement('afterend', bellContainer);
+ } else {
+ header.appendChild(bellContainer);
+ }
+
+ panel = document.createElement('div');
+ panel.className = 'notification-panel';
+ panel.innerHTML = `
+
+
+ `;
+ overlay = document.createElement('div');
+ overlay.className = 'panel-overlay';
+ document.body.appendChild(panel);
+ document.body.appendChild(overlay);
+
+ bellContainer.addEventListener('click', togglePanel);
+ overlay.addEventListener('click', closePanel);
+ panel.querySelector('.mark-all-read').addEventListener('click', markAllRead);
+ }
+
+ function updateBellBadge() {
+ const totalUnread = Object.values(notificationData).reduce((acc, mod) => acc + (mod.count || 0), 0);
+ const badge = bellContainer?.querySelector('.bell-badge');
+ if (badge) {
+ if (totalUnread > 0) {
+ badge.style.display = 'flex';
+ badge.textContent = totalUnread > 9 ? '9+' : totalUnread;
+ } else {
+ badge.style.display = 'none';
+ }
+ }
+ }
+
+ function togglePanel(e) {
+ e.stopPropagation();
+ isPanelOpen ? closePanel() : openPanel();
+ }
+
+ function openPanel() {
+ isPanelOpen = true;
+ panel.classList.add('open');
+ overlay.classList.add('show');
+ renderNotificationPanel();
+ }
+
+ function closePanel() {
+ isPanelOpen = false;
+ panel.classList.remove('open');
+ overlay.classList.remove('show');
+ }
+
+ function renderNotificationPanel() {
+ const list = panel.querySelector('.notification-list');
+ if (!list) return;
+
+ let items = [];
+ for (const [moduleId, modData] of Object.entries(notificationData)) {
+ if (modData.updates && modData.updates.length > 0) {
+ modData.updates.forEach((update) => {
+ items.push({
+ moduleId,
+ time: update.time,
+ summary: update.summary,
+ read: !modData.hasUpdate
+ });
+ });
+ }
+ }
+
+ if (items.length === 0) {
+ list.innerHTML = '暂无新通知';
+ return;
+ }
+
+ items.sort((a, b) => (a.time > b.time ? -1 : 1));
+
+ list.innerHTML = items.map(item => `
+
+ ${item.time}
+ ${item.moduleId}
+ ${item.summary}
+
+ `).join('');
+
+ list.querySelectorAll('.notification-item').forEach(item => {
+ item.addEventListener('click', (e) => {
+ const moduleId = item.dataset.module;
+ const card = findCardByModuleId(moduleId);
+ if (card) {
+ card.click();
+ }
+ markModuleAsRead(moduleId);
+ closePanel();
+ });
+ });
+ }
+
+ function findCardByModuleId(moduleId) {
+ const cards = getModuleCards();
+ for (let card of cards) {
+ let id = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!id) id = guessModuleIdFromCard(card);
+ if (id === moduleId) return card;
+ }
+ return null;
+ }
+
+ function markAllRead() {
+ for (let moduleId in notificationData) {
+ notificationData[moduleId].hasUpdate = false;
+ notificationData[moduleId].count = 0;
+ }
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notificationData));
+ renderUpdateBadges();
+ renderUnreadDots();
+ updateBellBadge();
+ renderNotificationPanel();
+ }
+
+ function fixHighlightClick() {
+ const container = document.querySelector('.modules-grid') || document.body;
+ container.addEventListener('click', (e) => {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ // 不做额外处理,只是确保事件冒泡
+ }
+ }, true);
+
+ const style = document.createElement('style');
+ style.textContent = `
+ .search-highlight {
+ pointer-events: none !important;
+ }
+ .module-card {
+ cursor: pointer;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ function init() {
+ if (!document.querySelector('link[href*="channel-notifications.css"]')) {
+ const link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = 'channel-notifications.css';
+ document.head.appendChild(link);
+ }
+
+ renderUpdateBadges();
+ renderUnreadDots();
+
+ createBell();
+ updateBellBadge();
+
+ fixHighlightClick();
+
+ document.addEventListener('click', (e) => {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ let moduleId = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!moduleId) moduleId = guessModuleIdFromCard(card);
+ if (moduleId) {
+ setTimeout(() => markModuleAsRead(moduleId), 100);
+ }
+ }
+ });
+
+ window.addEventListener('modulesRendered', () => {
+ renderUpdateBadges();
+ renderUnreadDots();
+ });
+
+ let lastCardCount = 0;
+ setInterval(() => {
+ const cards = getModuleCards();
+ if (cards.length !== lastCardCount) {
+ lastCardCount = cards.length;
+ renderUpdateBadges();
+ renderUnreadDots();
+ }
+ }, 2000);
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/modules/m-channel/channel-preferences.js b/modules/m-channel/channel-preferences.js
new file mode 100644
index 00000000..a4ee89c9
--- /dev/null
+++ b/modules/m-channel/channel-preferences.js
@@ -0,0 +1,194 @@
+// 频道偏好管理 - 记住用户的装修设置
+window.ChannelPreferences = {
+ STORAGE_KEY: 'hololake_channel_preferences',
+
+ // 默认配置
+ defaults: {
+ layout: 'grid', // grid, list, compact
+ theme: 'default', // default, ocean, forest, sunset, lavender
+ favorites: [], // 收藏的模块ID列表
+ moduleOrder: [], // 模块排序顺序(默认按ID)
+ stats: {} // 使用统计
+ },
+
+ // 当前配置
+ config: null,
+
+ // 初始化(加载配置)
+ init: function() {
+ console.log('[preferences] 初始化');
+ this.load();
+ return this.config;
+ },
+
+ // 加载配置
+ load: function() {
+ const saved = localStorage.getItem(this.STORAGE_KEY);
+ if (saved) {
+ try {
+ this.config = JSON.parse(saved);
+ console.log('[preferences] 加载配置:', this.config);
+ } catch (e) {
+ console.error('[preferences] 解析失败,使用默认配置', e);
+ this.config = { ...this.defaults };
+ }
+ } else {
+ console.log('[preferences] 无保存配置,使用默认');
+ this.config = { ...this.defaults };
+ }
+ return this.config;
+ },
+
+ // 保存配置
+ save: function() {
+ if (!this.config) return;
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));
+ console.log('[preferences] 保存配置:', this.config);
+ },
+
+ // 获取指定键的值
+ get: function(key) {
+ if (!this.config) this.load();
+ return this.config[key];
+ },
+
+ // 设置指定键的值(自动保存)
+ set: function(key, value) {
+ if (!this.config) this.load();
+ this.config[key] = value;
+ this.save();
+
+ // 触发事件总线通知
+ if (window.EventBus) {
+ EventBus.emit('preferences:changed', { key, value });
+ }
+ },
+
+ // 更新整个配置(合并)
+ update: function(newConfig) {
+ if (!this.config) this.load();
+ this.config = { ...this.config, ...newConfig };
+ this.save();
+ if (window.EventBus) {
+ EventBus.emit('preferences:changed', { full: this.config });
+ }
+ },
+
+ // 恢复默认设置
+ reset: function() {
+ this.config = { ...this.defaults };
+ this.save();
+ if (window.EventBus) {
+ EventBus.emit('preferences:reset', this.config);
+ }
+ console.log('[preferences] 恢复默认');
+ },
+
+ // 记录模块使用(用于统计)
+ recordUsage: function(moduleId) {
+ if (!this.config) this.load();
+ if (!this.config.stats) this.config.stats = {};
+ if (!this.config.stats[moduleId]) {
+ this.config.stats[moduleId] = {
+ count: 0,
+ lastUsed: null,
+ totalTime: 0
+ };
+ }
+ this.config.stats[moduleId].count++;
+ this.config.stats[moduleId].lastUsed = Date.now();
+ this.save();
+
+ // 开始计时(将在模块卸载时记录时长)
+ this.startTiming(moduleId);
+ },
+
+ // 计时相关
+ timing: {},
+ startTiming: function(moduleId) {
+ this.timing[moduleId] = Date.now();
+ },
+ stopTiming: function(moduleId) {
+ if (this.timing[moduleId]) {
+ const duration = (Date.now() - this.timing[moduleId]) / 1000; // 秒
+ if (this.config.stats[moduleId]) {
+ this.config.stats[moduleId].totalTime += duration;
+ this.save();
+ }
+ delete this.timing[moduleId];
+ }
+ },
+
+ // 获取统计报告
+ getStats: function() {
+ if (!this.config) this.load();
+ return this.config.stats || {};
+ },
+
+ // 获取收藏列表
+ getFavorites: function() {
+ return this.get('favorites') || [];
+ },
+
+ // 切换收藏状态
+ toggleFavorite: function(moduleId) {
+ let favorites = this.get('favorites') || [];
+ if (favorites.includes(moduleId)) {
+ favorites = favorites.filter(id => id !== moduleId);
+ } else {
+ favorites.push(moduleId);
+ }
+ this.set('favorites', favorites);
+ return favorites.includes(moduleId);
+ },
+
+ // 获取布局
+ getLayout: function() {
+ return this.get('layout') || 'grid';
+ },
+
+ // 设置布局
+ setLayout: function(layout) {
+ this.set('layout', layout);
+ },
+
+ // 获取主题
+ getTheme: function() {
+ return this.get('theme') || 'default';
+ },
+
+ // 设置主题
+ setTheme: function(theme) {
+ this.set('theme', theme);
+ },
+
+ // 获取模块排序
+ getModuleOrder: function() {
+ return this.get('moduleOrder') || [];
+ },
+
+ // 设置模块排序
+ setModuleOrder: function(order) {
+ this.set('moduleOrder', order);
+ },
+
+ // 重新排序(拖拽后调用)
+ reorderModules: function(fromIndex, toIndex) {
+ const order = this.getModuleOrder();
+ if (order.length === 0) {
+ // 如果还没有自定义顺序,则基于当前模块列表生成
+ const modules = window.ModuleRegistry ? ModuleRegistry.list() : [];
+ // 排除非显示模块(如调试面板)
+ const displayModules = modules.filter(m => ['m06','m08','m11','home','debug'].includes(m));
+ this.setModuleOrder(displayModules);
+ }
+ // 重新加载最新顺序
+ const currentOrder = this.getModuleOrder();
+ if (fromIndex < 0 || fromIndex >= currentOrder.length || toIndex < 0 || toIndex >= currentOrder.length) return;
+ const [moved] = currentOrder.splice(fromIndex, 1);
+ currentOrder.splice(toIndex, 0, moved);
+ this.setModuleOrder(currentOrder);
+ }
+};
+
+console.log('[preferences] 已加载');
diff --git a/modules/m-channel/channel-router.js b/modules/m-channel/channel-router.js
index 614fc3ed..3a51bc8c 100644
--- a/modules/m-channel/channel-router.js
+++ b/modules/m-channel/channel-router.js
@@ -1,98 +1,222 @@
-// ================== 路由配置 ==================
-const routes = {
- 'home': 'views/home.html',
- 'channel': 'views/channel.html',
- 'about': 'views/about.html'
-};
+// 频道路由(支持真实模块适配器 + 卡片列表首页 + 数据采集钩子)
+window.ChannelRouter = {
+ currentChannel: 'home',
+ contentEl: null,
+
+ init: function(contentElement) {
+ this.contentEl = contentElement;
+ console.log('[router] 初始化');
+
+ const savedState = ChannelState.restoreState();
+ if (savedState && savedState.currentChannel) {
+ this.currentChannel = savedState.currentChannel;
+ }
+
+ this.loadChannel(this.currentChannel);
+
+ if (savedState && savedState.visited) {
+ savedState.visited.forEach(channel => {
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ if (btn.dataset.channel === channel) {
+ btn.classList.add('visited');
+ }
+ });
+ });
+ }
-// 获取当前 hash 中的路径(去掉 #/)
-function getHashPath() {
- const hash = window.location.hash.slice(1) || '/';
- const path = hash.startsWith('/') ? hash.slice(1) : hash;
- return path || 'home';
-}
-
-// ================== 加载视图 ==================
-async function loadView(path) {
- const routerView = document.getElementById('router-view');
- if (!routerView) return;
-
- // 显示加载动画
- routerView.innerHTML = '';
-
- try {
- const viewFile = routes[path];
- if (!viewFile) {
- await load404(routerView);
+ // 页面关闭时结束计时
+ window.addEventListener('beforeunload', function() {
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.endSession();
+ }
+ });
+ },
+
+ navigateTo: function(channel) {
+ if (channel === this.currentChannel) return;
+
+ console.log(`[router] 导航到: ${channel}`);
+
+ this.contentEl.classList.add('fade-out');
+
+ setTimeout(() => {
+ this.loadChannel(channel);
+ this.contentEl.classList.remove('fade-out');
+ this.contentEl.classList.add('fade-in');
+ setTimeout(() => {
+ this.contentEl.classList.remove('fade-in');
+ }, 300);
+ }, 300);
+
+ this.currentChannel = channel;
+
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ btn.classList.remove('active');
+ if (btn.dataset.channel === channel) {
+ btn.classList.add('active');
+ }
+ });
+
+ ChannelState.saveState({
+ currentChannel: channel,
+ visited: this.getVisitedChannels()
+ });
+ },
+
+ loadChannel: function(channel) {
+ if (!this.contentEl) return;
+
+ // 数据采集钩子:开始加载
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadStart();
+ if (channel !== 'home' && channel !== 'debug' && channel !== 'dashboard') {
+ ChannelAnalytics.recordVisit(channel);
+ }
+ }
+
+ // 如果是真实模块(m06/m08/m11)
+ if (channel === 'm06' || channel === 'm08' || channel === 'm11') {
+ if (window.ModuleAdapter) {
+ ModuleAdapter.loadModule(channel, 'channel-content');
+ // 模块加载完成后记录加载时间
+ setTimeout(() => {
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadEnd(channel);
+ }
+ }, 100);
+ } else {
+ this.contentEl.innerHTML = '适配器未加载
';
+ }
+ ModuleLifecycle.onLoad(channel);
return;
}
-
- const response = await fetch(viewFile);
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}`);
+
+ // 数据面板路由(环节8新增)
+ if (channel === 'dashboard') {
+ fetch('views/channel-dashboard.html')
+ .then(response => response.text())
+ .then(html => {
+ this.contentEl.innerHTML = html;
+ // 渲染图表
+ if (typeof ChannelDashboard !== 'undefined') {
+ ChannelDashboard.render();
+ }
+ ModuleLifecycle.onLoad('dashboard');
+ // 记录加载完成
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadEnd('dashboard');
+ }
+ })
+ .catch(err => {
+ this.contentEl.innerHTML = '数据面板加载失败
';
+ console.error(err);
+ });
+ return;
}
- const html = await response.text();
- routerView.innerHTML = html;
- } catch (error) {
- console.error('加载视图失败:', error);
- routerView.innerHTML = `
-
- ❌ 加载失败:${error.message}
- 请检查文件是否存在,或刷新重试
-
- `;
- }
-
- // 更新导航高亮和状态栏
- updateActiveNav(path);
- updateStatusBar(path);
-}
-
-// 加载 404 页面
-async function load404(container) {
- try {
- const resp = await fetch('views/404.html');
- if (resp.ok) {
- container.innerHTML = await resp.text();
+
+ // 内置频道
+ if (channel === 'home') {
+ this.renderModuleCards();
+ } else if (channel === 'debug') {
+ this.loadDebugPanel();
} else {
- container.innerHTML = '⚠️ 404 - 页面未找到
';
+ this.contentEl.innerHTML = '未知频道
';
}
- } catch {
- container.innerHTML = '⚠️ 404 - 页面未找到
';
- }
-}
-// 更新导航高亮
-function updateActiveNav(path) {
- document.querySelectorAll('.nav-link').forEach(link => {
- link.classList.remove('active');
- const linkPath = link.getAttribute('href').slice(2);
- if (linkPath === path) {
- link.classList.add('active');
+ // 对于内置频道也记录加载完成
+ if (typeof ChannelAnalytics !== 'undefined' && channel !== 'dashboard') {
+ ChannelAnalytics.markLoadEnd(channel);
}
- });
-}
-
-// 更新状态栏
-function updateStatusBar(path) {
- const statusEl = document.getElementById('current-route');
- if (statusEl) {
- statusEl.textContent = `当前路由:/${path}`;
+ },
+
+ // 渲染所有模块卡片(用于首页)
+ renderModuleCards: function() {
+ const modules = [
+ { id: 'm06', name: '工单管理', icon: '📋', desc: '管理任务和工单' },
+ { id: 'm08', name: '数据统计', icon: '📊', desc: '查看数据统计和报表' },
+ { id: 'm11', name: '组件库', icon: '🧩', desc: '系统组件展示' },
+ { id: 'debug', name: '调试面板', icon: '🐞', desc: '查看事件和模块状态' },
+ { id: 'dashboard', name: '数据面板', icon: '📈', desc: '查看使用统计和性能' }
+ ];
+
+ let html = '';
+ modules.forEach(mod => {
+ const isFavorite = window.ChannelPreferences ?
+ ChannelPreferences.getFavorites().includes(mod.id) : false;
+ html += `
+
+ `;
+ });
+ html += '
';
+
+ this.contentEl.innerHTML = html;
+ ModuleLifecycle.onLoad('home');
+
+ // 触发收藏/拖拽重新初始化
+ if (window.ChannelFavorites) {
+ setTimeout(() => {
+ ChannelFavorites.init();
+ ChannelFavorites.initDragAndDrop();
+ }, 100);
+ }
+ },
+
+ loadDebugPanel: function() {
+ fetch('views/channel-debug.html')
+ .then(response => response.text())
+ .then(html => {
+ this.contentEl.innerHTML = html;
+ ModuleLifecycle.onLoad('debug');
+ this.initDebugPanel();
+ })
+ .catch(err => {
+ this.contentEl.innerHTML = '调试面板加载失败
';
+ });
+ },
+
+ initDebugPanel: function() {
+ const msgList = document.getElementById('debug-messages');
+ if (msgList && window.debugMessages) {
+ window.debugMessages.forEach(msg => {
+ const li = document.createElement('li');
+ li.textContent = `[${msg.time}] ${msg.event}: ${JSON.stringify(msg.data)}`;
+ msgList.appendChild(li);
+ });
+ }
+ const sendBtn = document.getElementById('debug-send');
+ const input = document.getElementById('debug-input');
+ if (sendBtn && input) {
+ sendBtn.addEventListener('click', () => {
+ const text = input.value.trim();
+ if (text) {
+ EventBus.emit('debug:message', { text, from: '调试面板' });
+ input.value = '';
+ }
+ });
+ }
+ const clearBtn = document.getElementById('debug-clear');
+ if (clearBtn) {
+ clearBtn.addEventListener('click', () => {
+ ChannelState.clearState();
+ alert('状态已清除,刷新页面生效');
+ });
+ }
+ },
+
+ getVisitedChannels: function() {
+ const visited = [];
+ document.querySelectorAll('.channel-btn.visited').forEach(btn => {
+ visited.push(btn.dataset.channel);
+ });
+ return visited;
}
-}
-
-// 监听 hash 变化
-window.addEventListener('hashchange', () => {
- const path = getHashPath();
- loadView(path);
-});
-
-// 首次加载
-window.addEventListener('DOMContentLoaded', () => {
- if (!window.location.hash) {
- window.location.hash = '#/home';
- } else {
- const path = getHashPath();
- loadView(path);
- }
-});
+};
diff --git a/modules/m-channel/channel-router.js.bak b/modules/m-channel/channel-router.js.bak
new file mode 100644
index 00000000..957038fb
--- /dev/null
+++ b/modules/m-channel/channel-router.js.bak
@@ -0,0 +1,190 @@
+// 频道路由(支持真实模块适配器 + 卡片列表首页)
+window.ChannelRouter = {
+ currentChannel: 'home',
+ contentEl: null,
+
+ init: function(contentElement) {
+ this.contentEl = contentElement;
+ console.log('[router] 初始化');
+
+ const savedState = ChannelState.restoreState();
+ if (savedState && savedState.currentChannel) {
+ this.currentChannel = savedState.currentChannel;
+ }
+
+ this.loadChannel(this.currentChannel);
+
+ if (savedState && savedState.visited) {
+ savedState.visited.forEach(channel => {
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ if (btn.dataset.channel === channel) {
+ btn.classList.add('visited');
+ }
+ });
+ });
+ }
+
+ // 【新增】页面关闭时结束计时
+ window.addEventListener('beforeunload', function() {
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.endSession();
+ }
+ });
+ },
+
+ navigateTo: function(channel) {
+ if (channel === this.currentChannel) return;
+
+ console.log(`[router] 导航到: ${channel}`);
+
+ this.contentEl.classList.add('fade-out');
+
+ setTimeout(() => {
+ this.loadChannel(channel);
+ this.contentEl.classList.remove('fade-out');
+ this.contentEl.classList.add('fade-in');
+ setTimeout(() => {
+ this.contentEl.classList.remove('fade-in');
+ }, 300);
+ }, 300);
+
+ this.currentChannel = channel;
+
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ btn.classList.remove('active');
+ if (btn.dataset.channel === channel) {
+ btn.classList.add('active');
+ }
+ });
+
+ ChannelState.saveState({
+ currentChannel: channel,
+ visited: this.getVisitedChannels()
+ });
+ },
+
+ loadChannel: function(channel) {
+ if (!this.contentEl) return;
+
+ // 如果是真实模块(m06/m08/m11)
+ if (channel === 'm06' || channel === 'm08' || channel === 'm11') {
+ // 【新增】数据采集钩子:标记加载开始并记录访问
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadStart();
+ ChannelAnalytics.recordVisit(channel);
+ }
+
+ if (window.ModuleAdapter) {
+ ModuleAdapter.loadModule(channel, 'channel-content');
+ } else {
+ this.contentEl.innerHTML = '适配器未加载
';
+ }
+ ModuleLifecycle.onLoad(channel);
+
+ // 【新增】数据采集钩子:标记加载完成
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadEnd(channel);
+ }
+ return;
+ }
+
+ // 内置频道
+ if (channel === 'home') {
+ this.renderModuleCards(); // 显示模块卡片列表
+ } else if (channel === 'debug') {
+ this.loadDebugPanel();
+ } else {
+ this.contentEl.innerHTML = '未知频道
';
+ }
+ },
+
+ // 渲染所有模块卡片(用于首页)
+ renderModuleCards: function() {
+ const modules = [
+ { id: 'm06', name: '工单管理', icon: '📋', desc: '管理任务和工单' },
+ { id: 'm08', name: '数据统计', icon: '📊', desc: '查看数据统计和报表' },
+ { id: 'm11', name: '组件库', icon: '🧩', desc: '系统组件展示' },
+ { id: 'debug', name: '调试面板', icon: '🐞', desc: '查看事件和模块状态' }
+ ];
+
+ let html = '';
+ modules.forEach(mod => {
+ const isFavorite = window.ChannelPreferences ?
+ ChannelPreferences.getFavorites().includes(mod.id) : false;
+ html += `
+
+ `;
+ });
+ html += '
';
+
+ this.contentEl.innerHTML = html;
+ ModuleLifecycle.onLoad('home');
+
+ // 触发收藏/拖拽重新初始化
+ if (window.ChannelFavorites) {
+ setTimeout(() => {
+ ChannelFavorites.init();
+ ChannelFavorites.initDragAndDrop();
+ }, 100);
+ }
+ },
+
+ loadDebugPanel: function() {
+ fetch('views/channel-debug.html')
+ .then(response => response.text())
+ .then(html => {
+ this.contentEl.innerHTML = html;
+ ModuleLifecycle.onLoad('debug');
+ this.initDebugPanel();
+ })
+ .catch(err => {
+ this.contentEl.innerHTML = '调试面板加载失败
';
+ });
+ },
+
+ initDebugPanel: function() {
+ const msgList = document.getElementById('debug-messages');
+ if (msgList && window.debugMessages) {
+ window.debugMessages.forEach(msg => {
+ const li = document.createElement('li');
+ li.textContent = `[${msg.time}] ${msg.event}: ${JSON.stringify(msg.data)}`;
+ msgList.appendChild(li);
+ });
+ }
+ const sendBtn = document.getElementById('debug-send');
+ const input = document.getElementById('debug-input');
+ if (sendBtn && input) {
+ sendBtn.addEventListener('click', () => {
+ const text = input.value.trim();
+ if (text) {
+ EventBus.emit('debug:message', { text, from: '调试面板' });
+ input.value = '';
+ }
+ });
+ }
+ const clearBtn = document.getElementById('debug-clear');
+ if (clearBtn) {
+ clearBtn.addEventListener('click', () => {
+ ChannelState.clearState();
+ alert('状态已清除,刷新页面生效');
+ });
+ }
+ },
+
+ getVisitedChannels: function() {
+ const visited = [];
+ document.querySelectorAll('.channel-btn.visited').forEach(btn => {
+ visited.push(btn.dataset.channel);
+ });
+ return visited;
+ }
+};
diff --git a/modules/m-channel/channel-router.js.bakcat b/modules/m-channel/channel-router.js.bakcat
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/m-channel/channel-state.js b/modules/m-channel/channel-state.js
new file mode 100644
index 00000000..38edffd6
--- /dev/null
+++ b/modules/m-channel/channel-state.js
@@ -0,0 +1,44 @@
+// 状态管理(localStorage)
+window.ChannelState = {
+ STORAGE_KEY: 'hololake_channel_state',
+
+ // 保存状态
+ saveState: function(state) {
+ const data = {
+ ...state,
+ timestamp: Date.now(),
+ visited: state.visited || []
+ };
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
+ console.log('[state] 保存状态:', data);
+ },
+
+ // 恢复状态
+ restoreState: function() {
+ const saved = localStorage.getItem(this.STORAGE_KEY);
+ if (!saved) return null;
+ try {
+ const state = JSON.parse(saved);
+ console.log('[state] 恢复状态:', state);
+ return state;
+ } catch (e) {
+ console.error('[state] 解析失败:', e);
+ return null;
+ }
+ },
+
+ // 标记已访问
+ markVisited: function(channel) {
+ const state = this.restoreState() || { visited: [] };
+ if (!state.visited.includes(channel)) {
+ state.visited.push(channel);
+ this.saveState(state);
+ }
+ },
+
+ // 清除状态
+ clearState: function() {
+ localStorage.removeItem(this.STORAGE_KEY);
+ console.log('[state] 已清除');
+ }
+};
diff --git a/modules/m-channel/channel-stats.js b/modules/m-channel/channel-stats.js
new file mode 100644
index 00000000..d4263fe1
--- /dev/null
+++ b/modules/m-channel/channel-stats.js
@@ -0,0 +1,298 @@
+// 频道使用统计管理
+window.ChannelStats = {
+ // 初始化
+ init: function() {
+ console.log('[stats] 初始化');
+ this.bindEvents();
+ },
+
+ // 绑定事件
+ bindEvents: function() {
+ if (!window.EventBus) return;
+
+ // 监听模块加载,开始计时
+ EventBus.on('module:loaded', (data) => {
+ if (data && data.module) {
+ this.recordView(data.module);
+ }
+ });
+
+ // 监听模块卸载,结束计时
+ EventBus.on('module:unloaded', (data) => {
+ if (data && data.module) {
+ if (window.ChannelPreferences) {
+ ChannelPreferences.stopTiming(data.module);
+ }
+ }
+ });
+
+ // 监听页面关闭,停止所有计时
+ window.addEventListener('beforeunload', () => {
+ if (window.ChannelPreferences && window.ChannelPreferences.timing) {
+ Object.keys(ChannelPreferences.timing).forEach(moduleId => {
+ ChannelPreferences.stopTiming(moduleId);
+ });
+ }
+ });
+ },
+
+ // 记录模块访问
+ recordView: function(moduleId) {
+ if (!window.ChannelPreferences) return;
+
+ // 记录使用次数和开始计时
+ ChannelPreferences.recordUsage(moduleId);
+
+ // 发送事件
+ if (window.EventBus) {
+ EventBus.emit('stats:recorded', {
+ module: moduleId,
+ time: Date.now()
+ });
+ }
+ },
+
+ // 获取统计报告
+ getStatsReport: function() {
+ if (!window.ChannelPreferences) return {};
+
+ const stats = ChannelPreferences.getStats();
+ const modules = window.ModuleRegistry ? ModuleRegistry.list() : [];
+
+ // 格式化统计数据
+ const report = {
+ totalViews: 0,
+ mostUsed: null,
+ lastUsed: null,
+ moduleDetails: {}
+ };
+
+ let maxCount = 0;
+ let lastTime = 0;
+
+ modules.forEach(moduleId => {
+ const moduleStats = stats[moduleId] || { count: 0, lastUsed: null, totalTime: 0 };
+ report.moduleDetails[moduleId] = {
+ count: moduleStats.count,
+ lastUsed: moduleStats.lastUsed ? new Date(moduleStats.lastUsed).toLocaleString() : '从未使用',
+ totalTime: moduleStats.totalTime ? this.formatTime(moduleStats.totalTime) : '0秒'
+ };
+
+ report.totalViews += moduleStats.count;
+
+ if (moduleStats.count > maxCount) {
+ maxCount = moduleStats.count;
+ report.mostUsed = moduleId;
+ }
+
+ if (moduleStats.lastUsed && moduleStats.lastUsed > lastTime) {
+ lastTime = moduleStats.lastUsed;
+ report.lastUsed = moduleId;
+ }
+ });
+
+ return report;
+ },
+
+ // 格式化时间(秒 -> 可读格式)
+ formatTime: function(seconds) {
+ if (seconds < 60) return `${Math.round(seconds)}秒`;
+ if (seconds < 3600) {
+ const minutes = Math.floor(seconds / 60);
+ const secs = Math.round(seconds % 60);
+ return `${minutes}分${secs}秒`;
+ }
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ return `${hours}小时${minutes}分`;
+ },
+
+ // 渲染统计面板
+ renderStatsPanel: function(container) {
+ if (!container) return;
+
+ const report = this.getStatsReport();
+ const modules = window.ModuleRegistry ? ModuleRegistry.list() : [];
+
+ let html = `
+
+
📊 使用统计
+
+
+ 总访问次数:
+ ${report.totalViews}
+
+ ${report.mostUsed ? `
+
+ 最常用模块:
+ ${this.getModuleName(report.mostUsed)} (${report.moduleDetails[report.mostUsed]?.count || 0}次)
+
+ ` : ''}
+ ${report.lastUsed ? `
+
+ 最近使用:
+ ${this.getModuleName(report.lastUsed)}
+
+ ` : ''}
+
+
+
模块详情
+
+
+
+ | 模块 |
+ 访问次数 |
+ 累计使用时长 |
+ 最后使用 |
+
+
+
+ `;
+
+ modules.forEach(moduleId => {
+ const detail = report.moduleDetails[moduleId] || { count: 0, totalTime: '0秒', lastUsed: '从未使用' };
+ html += `
+
+ | ${this.getModuleName(moduleId)} |
+ ${detail.count} |
+ ${detail.totalTime} |
+ ${detail.lastUsed} |
+
+ `;
+ });
+
+ html += `
+
+
+
+
+
+
+ `;
+
+ container.innerHTML = html;
+
+ // 绑定重置按钮
+ const resetBtn = document.getElementById('reset-stats-btn');
+ if (resetBtn) {
+ resetBtn.addEventListener('click', () => {
+ if (confirm('确定要重置所有统计数据吗?')) {
+ this.resetStats();
+ }
+ });
+ }
+ },
+
+ // 获取模块显示名称
+ getModuleName: function(moduleId) {
+ const names = {
+ 'm06': '工单管理',
+ 'm08': '数据统计',
+ 'm11': '组件库',
+ 'home': '首页',
+ 'debug': '调试面板'
+ };
+ return names[moduleId] || moduleId;
+ },
+
+ // 重置统计
+ resetStats: function() {
+ if (!window.ChannelPreferences) return;
+
+ const config = ChannelPreferences.config;
+ if (config) {
+ config.stats = {};
+ ChannelPreferences.save();
+
+ if (window.EventBus) {
+ EventBus.emit('stats:reset');
+ }
+
+ alert('统计数据已重置');
+
+ // 重新渲染统计面板
+ const container = document.querySelector('.stats-panel-container');
+ if (container) {
+ this.renderStatsPanel(container);
+ }
+ }
+ },
+
+ // 注入样式
+ injectStyles: function() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .stats-panel {
+ padding: 20px;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ }
+ .stats-summary {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 16px;
+ margin: 20px 0;
+ padding: 16px;
+ background: #f8fafc;
+ border-radius: 8px;
+ }
+ .stat-item {
+ font-size: 14px;
+ }
+ .stat-label {
+ color: #64748b;
+ }
+ .stat-value {
+ font-weight: 600;
+ color: #0f172a;
+ margin-left: 8px;
+ }
+ .stats-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 20px 0;
+ }
+ .stats-table th,
+ .stats-table td {
+ padding: 12px;
+ text-align: left;
+ border-bottom: 1px solid #e2e8f0;
+ }
+ .stats-table th {
+ background: #f1f5f9;
+ font-weight: 600;
+ color: #334155;
+ }
+ .stats-table tr:hover {
+ background: #f8fafc;
+ }
+ .stats-actions {
+ text-align: right;
+ margin-top: 20px;
+ }
+ .stats-reset-btn {
+ padding: 8px 16px;
+ background: #ef4444;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ }
+ .stats-reset-btn:hover {
+ background: #dc2626;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+};
+
+// 自动注入样式
+setTimeout(() => {
+ if (window.ChannelStats) {
+ ChannelStats.injectStyles();
+ }
+}, 100);
+
+console.log('[stats] 已加载');
diff --git a/modules/m-channel/channel-style.css b/modules/m-channel/channel-style.css
index 24da6655..b03191ef 100644
--- a/modules/m-channel/channel-style.css
+++ b/modules/m-channel/channel-style.css
@@ -1,150 +1,62 @@
-/* 基础重置 */
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
- background: #f5f7fb;
- color: #1e293b;
- line-height: 1.5;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
-}
-
-/* 导航栏 */
-.channel-nav {
- background: white;
- padding: 1rem 2rem;
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
- display: flex;
- gap: 2rem;
- border-bottom: 1px solid #e2e8f0;
-}
-
-.nav-link {
- text-decoration: none;
- color: #64748b;
- font-weight: 500;
- padding: 0.5rem 0;
- border-bottom: 2px solid transparent;
- transition: all 0.2s;
-}
-
-.nav-link:hover {
- color: #0284c7;
-}
-
-.nav-link.active {
- color: #0284c7;
- border-bottom-color: #0284c7;
-}
-
-/* 路由视图容器 */
-.router-view {
- flex: 1;
- padding: 2rem;
+/* 频道基础样式 */
+.channel-container {
max-width: 1200px;
margin: 0 auto;
- width: 100%;
- background: white;
- border-radius: 12px 12px 0 0;
- box-shadow: 0 -4px 12px rgba(0,0,0,0.02);
+ padding: 20px;
+ font-family: system-ui, -apple-system, sans-serif;
}
-/* 状态栏 */
-.status-bar {
- background: #1e293b;
- color: #94a3b8;
- padding: 0.75rem 2rem;
- font-size: 0.9rem;
- text-align: center;
- border-top: 1px solid #334155;
+.channel-nav {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 20px;
+ border-bottom: 2px solid #e5e7eb;
+ padding-bottom: 10px;
}
-/* 卡片网格 */
-.card-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 1.5rem;
- margin-top: 1.5rem;
-}
-
-.module-card {
- background: #f8fafc;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- padding: 1.5rem 1rem;
- text-align: center;
- cursor: pointer;
- transition: all 0.2s;
- box-shadow: 0 2px 4px rgba(0,0,0,0.02);
-}
-
-.module-card:hover {
- transform: translateY(-4px);
- border-color: #0284c7;
- box-shadow: 0 12px 20px -8px rgba(2, 132, 199, 0.2);
-}
-
-.module-card h3 {
- margin-bottom: 0.5rem;
- color: #0f172a;
-}
-
-.module-card p {
- font-size: 0.9rem;
- color: #64748b;
-}
-
-/* 加载动画 */
-.loader {
- display: inline-block;
- width: 24px;
- height: 24px;
- border: 3px solid #e2e8f0;
- border-top-color: #0284c7;
- border-radius: 50%;
- animation: spin 0.8s linear infinite;
-}
-
-@keyframes spin {
- to { transform: rotate(360deg); }
-}
-
-/* 错误提示 */
-.error-message {
- background: #fee2e2;
- border: 1px solid #ef4444;
- color: #b91c1c;
- padding: 1rem;
+.channel-btn {
+ padding: 10px 20px;
+ border: none;
+ background: #f3f4f6;
border-radius: 8px;
- margin: 1rem 0;
-}
-
-/* 模块内容区 */
-.module-content {
- margin-top: 2rem;
- border-top: 1px dashed #cbd5e1;
- padding-top: 2rem;
-}
-
-.back-button {
- background: none;
- border: 1px solid #cbd5e1;
- padding: 0.5rem 1.5rem;
- border-radius: 30px;
cursor: pointer;
- font-size: 0.95rem;
- color: #475569;
+ font-size: 16px;
transition: all 0.2s;
- margin-bottom: 1rem;
}
-.back-button:hover {
- background: #f1f5f9;
- border-color: #94a3b8;
+.channel-btn:hover {
+ background: #e5e7eb;
+}
+
+.channel-btn.active {
+ background: #3b82f6;
+ color: white;
+}
+
+.channel-btn.visited {
+ position: relative;
+}
+
+.channel-btn.visited::after {
+ content: "✓";
+ position: absolute;
+ top: -5px;
+ right: -5px;
+ background: #10b981;
+ color: white;
+ border-radius: 50%;
+ width: 20px;
+ height: 20px;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.channel-content {
+ min-height: 400px;
+ padding: 20px;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
diff --git a/modules/m-channel/channel-theme.js b/modules/m-channel/channel-theme.js
new file mode 100644
index 00000000..24ee8950
--- /dev/null
+++ b/modules/m-channel/channel-theme.js
@@ -0,0 +1,157 @@
+// 频道主题管理 - 换主题色
+window.ChannelTheme = {
+ // 预设主题
+ themes: {
+ default: {
+ name: '默认蓝',
+ colors: {
+ primary: '#3b82f6',
+ primaryDark: '#2563eb',
+ secondary: '#10b981',
+ background: '#ffffff',
+ surface: '#f9fafb',
+ text: '#1f2937',
+ textLight: '#6b7280',
+ border: '#e5e7eb'
+ }
+ },
+ ocean: {
+ name: '海洋',
+ colors: {
+ primary: '#0891b2',
+ primaryDark: '#0e7490',
+ secondary: '#2dd4bf',
+ background: '#ecfeff',
+ surface: '#ffffff',
+ text: '#164e63',
+ textLight: '#155e75',
+ border: '#a5f3fc'
+ }
+ },
+ forest: {
+ name: '森林',
+ colors: {
+ primary: '#059669',
+ primaryDark: '#047857',
+ secondary: '#fbbf24',
+ background: '#f0fdf4',
+ surface: '#ffffff',
+ text: '#064e3b',
+ textLight: '#065f46',
+ border: '#a7f3d0'
+ }
+ },
+ sunset: {
+ name: '日落',
+ colors: {
+ primary: '#d97706',
+ primaryDark: '#b45309',
+ secondary: '#f43f5e',
+ background: '#fff7ed',
+ surface: '#ffffff',
+ text: '#7c2d12',
+ textLight: '#9a3412',
+ border: '#fed7aa'
+ }
+ },
+ lavender: {
+ name: '薰衣草',
+ colors: {
+ primary: '#8b5cf6',
+ primaryDark: '#7c3aed',
+ secondary: '#ec4899',
+ background: '#f5f3ff',
+ surface: '#ffffff',
+ text: '#4c1d95',
+ textLight: '#5b21b6',
+ border: '#ddd6fe'
+ }
+ }
+ },
+
+ // 当前主题ID
+ currentTheme: 'default',
+
+ // 初始化主题
+ init: function() {
+ console.log('[theme] 初始化');
+
+ // 从偏好设置加载主题
+ if (window.ChannelPreferences) {
+ const savedTheme = ChannelPreferences.getTheme();
+ if (savedTheme && this.themes[savedTheme]) {
+ this.currentTheme = savedTheme;
+ }
+ }
+
+ // 应用主题
+ this.applyTheme(this.currentTheme);
+
+ // 监听主题变化事件
+ if (window.EventBus) {
+ EventBus.on('preferences:changed', (data) => {
+ if (data.key === 'theme') {
+ this.applyTheme(data.value);
+ }
+ });
+ }
+ },
+
+ // 应用主题
+ applyTheme: function(themeId) {
+ if (!this.themes[themeId]) {
+ console.error(`[theme] 未知主题: ${themeId}`);
+ return;
+ }
+
+ this.currentTheme = themeId;
+ const colors = this.themes[themeId].colors;
+
+ // 设置 CSS 自定义属性
+ const root = document.documentElement;
+ for (const [key, value] of Object.entries(colors)) {
+ root.style.setProperty(`--theme-${key}`, value);
+ }
+
+ console.log(`[theme] 应用主题: ${themeId}`);
+
+ // 触发事件
+ if (window.EventBus) {
+ EventBus.emit('theme:changed', { theme: themeId, colors });
+ }
+ },
+
+ // 切换主题
+ setTheme: function(themeId) {
+ if (!this.themes[themeId]) return;
+
+ this.applyTheme(themeId);
+
+ // 保存到偏好设置
+ if (window.ChannelPreferences) {
+ ChannelPreferences.setTheme(themeId);
+ }
+ },
+
+ // 获取所有主题列表
+ getThemes: function() {
+ return Object.entries(this.themes).map(([id, theme]) => ({
+ id,
+ name: theme.name
+ }));
+ },
+
+ // 获取当前主题名称
+ getCurrentThemeName: function() {
+ return this.themes[this.currentTheme]?.name || '默认蓝';
+ }
+};
+
+// 自动初始化
+setTimeout(() => {
+ if (window.ChannelTheme) {
+ ChannelTheme.init();
+ }
+}, 200);
+
+console.log('[theme] 已加载');
diff --git a/modules/m-channel/channel-transition.css b/modules/m-channel/channel-transition.css
new file mode 100644
index 00000000..d705abd0
--- /dev/null
+++ b/modules/m-channel/channel-transition.css
@@ -0,0 +1,48 @@
+/* 过渡动画 */
+.channel-content {
+ transition: opacity 0.3s ease-in-out;
+}
+
+.channel-content.fade-out {
+ opacity: 0;
+}
+
+.channel-content.fade-in {
+ opacity: 1;
+}
+
+/* 滑动动画 */
+.slide-left-enter {
+ transform: translateX(100%);
+}
+
+.slide-left-enter-active {
+ transform: translateX(0);
+ transition: transform 0.3s ease-in-out;
+}
+
+.slide-right-enter {
+ transform: translateX(-100%);
+}
+
+.slide-right-enter-active {
+ transform: translateX(0);
+ transition: transform 0.3s ease-in-out;
+}
+
+.slide-exit {
+ position: absolute;
+ width: 100%;
+}
+
+.slide-exit-active {
+ transition: transform 0.3s ease-in-out;
+}
+
+.slide-exit-left {
+ transform: translateX(-100%);
+}
+
+.slide-exit-right {
+ transform: translateX(100%);
+}
diff --git a/modules/m-channel/channel-ultimate.html b/modules/m-channel/channel-ultimate.html
new file mode 100644
index 00000000..346968f7
--- /dev/null
+++ b/modules/m-channel/channel-ultimate.html
@@ -0,0 +1,1401 @@
+
+
+
+
+
+ 光湖频道 · 终极版
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
主题色
+
+
+
+
+
+
+
+
+
+
统计数据
+
+
+
+
其他
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/m-channel/error-boundary.js b/modules/m-channel/error-boundary.js
new file mode 100644
index 00000000..9ea7037d
--- /dev/null
+++ b/modules/m-channel/error-boundary.js
@@ -0,0 +1,134 @@
+// 错误边界 - 保险丝
+window.ErrorBoundary = {
+ // 包裹一个可能出错的渲染函数
+ wrap: function(renderFn, fallbackComponent, moduleId) {
+ return function(container) {
+ try {
+ return renderFn(container);
+ } catch (error) {
+ console.error(`[error-boundary] 模块 ${moduleId} 渲染失败:`, error);
+ return this.showFallback(container, moduleId, error);
+ }
+ };
+ },
+
+ // 显示降级界面
+ showFallback: function(container, moduleId, error) {
+ if (!container) return;
+
+ container.innerHTML = `
+
+
⚠️
+
+
模块 ${moduleId} 加载失败
+
${error.message || '未知错误'}
+
+
+
+
+
+
+ `;
+
+ // 记录错误到事件总线
+ if (window.EventBus) {
+ EventBus.emit('module:error', {
+ module: moduleId,
+ error: error.message,
+ time: Date.now()
+ });
+ }
+ },
+
+ // 重新加载模块(通过适配器)
+ reloadModule: function(moduleId) {
+ console.log(`[error-boundary] 尝试重载模块: ${moduleId}`);
+
+ // 触发全局重载事件
+ if (window.EventBus) {
+ EventBus.emit('module:reload', { module: moduleId });
+ }
+
+ // 如果存在适配器,调用适配器的重载方法
+ if (window.ModuleAdapter && window.ModuleAdapter.reloadModule) {
+ const container = document.querySelector(`[data-module="${moduleId}"]`) ||
+ document.getElementById(`module-${moduleId}`);
+ if (container) {
+ ModuleAdapter.reloadModule(moduleId, container.id);
+ }
+ } else {
+ // 否则直接刷新页面(简易方案)
+ location.reload();
+ }
+ },
+
+ // 隐藏错误(仅当用户点击“忽略”时)
+ hideError: function(btn) {
+ const errorDiv = btn.closest('.error-boundary');
+ if (errorDiv) {
+ errorDiv.style.display = 'none';
+ }
+ },
+
+ // 添加一些基础样式(会在 channel-style.css 中补充)
+ injectStyles: function() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .error-boundary {
+ padding: 30px;
+ text-align: center;
+ background: #fff3f3;
+ border: 1px solid #ffcdd2;
+ border-radius: 8px;
+ margin: 20px 0;
+ }
+ .error-icon {
+ font-size: 48px;
+ margin-bottom: 15px;
+ }
+ .error-message h4 {
+ color: #d32f2f;
+ margin: 0 0 10px;
+ }
+ .error-message p {
+ color: #666;
+ margin: 0 0 20px;
+ }
+ .error-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ }
+ .error-retry {
+ padding: 8px 16px;
+ background: #d32f2f;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ .error-dismiss {
+ padding: 8px 16px;
+ background: #9e9e9e;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+};
+
+// 自动注入样式
+setTimeout(() => {
+ if (window.ErrorBoundary) {
+ ErrorBoundary.injectStyles();
+ }
+}, 100);
+
+console.log('[error-boundary] 已加载');
diff --git a/modules/m-channel/event-bus.js b/modules/m-channel/event-bus.js
new file mode 100644
index 00000000..a4b09f2e
--- /dev/null
+++ b/modules/m-channel/event-bus.js
@@ -0,0 +1,37 @@
+// 事件总线(群聊频道)
+window.EventBus = {
+ listeners: {},
+
+ // 订阅(加入群聊)
+ on: function(event, callback) {
+ if (!this.listeners[event]) {
+ this.listeners[event] = [];
+ }
+ this.listeners[event].push(callback);
+ console.log(`[事件总线] 订阅事件: ${event}`);
+ },
+
+ // 取消订阅(退群)
+ off: function(event, callback) {
+ if (!this.listeners[event]) return;
+ if (!callback) {
+ delete this.listeners[event];
+ } else {
+ this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
+ }
+ console.log(`[事件总线] 取消订阅: ${event}`);
+ },
+
+ // 发送消息(@所有人)
+ emit: function(event, data) {
+ console.log(`[事件总线] 发送事件: ${event}`, data);
+ if (!this.listeners[event]) return;
+ this.listeners[event].forEach(callback => {
+ try {
+ callback(data);
+ } catch (e) {
+ console.error(`[事件总线] 执行回调出错: ${e}`);
+ }
+ });
+ }
+};
diff --git a/modules/m-channel/index.html b/modules/m-channel/index.html
index c0574ad8..59dc02c1 100644
--- a/modules/m-channel/index.html
+++ b/modules/m-channel/index.html
@@ -3,31 +3,40 @@
- M-CHANNEL · 用户频道引擎
+ 光湖频道 · 动态渲染引擎
+
+
-
-
+
+
+
+
+
+
-
-
- ✨ 加载中...
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+