diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index 6d37d9af..155897f3 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-14T13:47:35.614Z",
"issues": [
{
"id": "BS-001",
@@ -15,7 +15,7 @@
"backend/schemas/",
".github/brain/routing-map.json"
],
- "last_seen": "2026-03-13",
+ "last_seen": "2026-03-14",
"source": "routing-map.json 统计"
},
{
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index 80f1b9bc..b80a76ae 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-14T13:47:35.615Z
---
@@ -56,7 +56,7 @@
### 仓库统计
- 功能模块:10 个
-- Workflow:37 个
+- Workflow:44 个
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-13T06:03:28.752Z
+- **编译时间**:2026-03-14T13:47:35.615Z
- **脑文件规则版本**: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 | 44 个 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..217f529b 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-14T13:47:35.614Z",
"health": {
"brain_consistency": {
"status": "yellow",
@@ -13,7 +13,7 @@
},
"workflow_health": {
"status": "green",
- "detail": "37 个 workflow 已注册"
+ "detail": "44 个 workflow 已注册"
},
"routing_health": {
"status": "yellow",
diff --git a/.github/brain/bulletin-board-today.json b/.github/brain/bulletin-board-today.json
new file mode 100644
index 00000000..1c17f1ce
--- /dev/null
+++ b/.github/brain/bulletin-board-today.json
@@ -0,0 +1,41 @@
+{
+ "date": "2026-03-14",
+ "records": [
+ {
+ "key": "Copilot|—|2026-03-14T08:25:24Z",
+ "ts": "2026-03-14T08:25:24Z",
+ "actor": "Copilot",
+ "module": "—",
+ "result": "success",
+ "icon": "✅",
+ "sortKey": 1773476724000
+ },
+ {
+ "key": "冰朔|—|2026-03-14T08:25:36Z",
+ "ts": "2026-03-14T08:25:36Z",
+ "actor": "冰朔",
+ "module": "—",
+ "result": "success",
+ "icon": "✅",
+ "sortKey": 1773476736000
+ },
+ {
+ "key": "Copilot|—|2026-03-14T13:47:27Z",
+ "ts": "2026-03-14T13:47:27Z",
+ "actor": "Copilot",
+ "module": "—",
+ "result": "success",
+ "icon": "✅",
+ "sortKey": 1773496047000
+ },
+ {
+ "key": "冰朔|—|2026-03-14T13:47:35Z",
+ "ts": "2026-03-14T13:47:35Z",
+ "actor": "冰朔",
+ "module": "—",
+ "result": "success",
+ "icon": "✅",
+ "sortKey": 1773496055000
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.github/brain/memory.json b/.github/brain/memory.json
index cdc4885d..e2548573 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-14T08:48:40.855Z",
"wake_protocol_version": "v3.0",
"brain_version": "v3.0",
"architecture": {
@@ -67,6 +67,18 @@
}
},
"events": [
+ {
+ "date": "2026-03-14",
+ "type": "psp_inspection",
+ "description": "铸渊 PSP 巡检通过 · 全部检查项 ✅",
+ "by": "铸渊PSP巡检"
+ },
+ {
+ "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",
@@ -185,6 +197,22 @@
"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"
+ },
+ {
+ "timestamp": "2026-03-14T08:48:40.855Z",
+ "type": "daily_check",
+ "result": "passed",
+ "actor": "qinfendebingshuo",
+ "ref": "refs/heads/main",
+ "run_id": "23084594270"
}
]
-}
\ No newline at end of file
+}
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index 8ec2133c..aabb0d73 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,20 +1,20 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-13T06:03:44.081Z",
+ "generated_at": "2026-03-14T13:47:45.215Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
"zones": 13,
"total_modules": 10,
- "total_workflows": 37,
- "total_scripts": 33,
+ "total_workflows": 44,
+ "total_scripts": 45,
"total_dev_nodes": 8,
"hli_interfaces": 21,
"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-14T08:48:40.855Z"
},
"zones": [
{
@@ -57,6 +57,9 @@
{
"file": "bingshuo-system-health.json"
},
+ {
+ "file": "bulletin-board-today.json"
+ },
{
"file": "collaborators.json"
},
@@ -88,7 +91,7 @@
"file": "wake-protocol.md"
}
],
- "item_count": 18
+ "item_count": 19
},
{
"zone_id": "PERSONA_BRAIN",
@@ -218,6 +221,14 @@
"pull_request"
]
},
+ {
+ "file": "daily-maintenance.yml",
+ "name": "\"🔧 铸渊 · Daily Maintenance Agent\"",
+ "triggers": [
+ "schedule(0 2 * * *)",
+ "manual"
+ ]
+ },
{
"file": "deploy-pages.yml",
"name": "🌀 部署铸渊聊天室 (GitHub Pages)",
@@ -250,6 +261,21 @@
"manual"
]
},
+ {
+ "file": "execution-sync.yml",
+ "name": "\"📡 铸渊 · 执行层状态同步\"",
+ "triggers": [
+ "schedule(0 3 * * *)",
+ "manual"
+ ]
+ },
+ {
+ "file": "feishu-syslog-bridge.yml",
+ "name": "铸渊 · 飞书SYSLOG桥接",
+ "triggers": [
+ "unknown"
+ ]
+ },
{
"file": "generate-module-doc.yml",
"name": "铸渊 · 光湖纪元 模块文档自动生成",
@@ -266,6 +292,13 @@
"pull_request"
]
},
+ {
+ "file": "notion-callback-pipeline.yml",
+ "name": "Notion Callback Pipeline",
+ "triggers": [
+ "unknown"
+ ]
+ },
{
"file": "notion-connectivity-test.yml",
"name": "铸渊 · Notion 连通性测试",
@@ -273,6 +306,14 @@
"manual"
]
},
+ {
+ "file": "notion-heartbeat.yml",
+ "name": "Notion Heartbeat Monitor",
+ "triggers": [
+ "schedule(*/5 * * * *)",
+ "manual"
+ ]
+ },
{
"file": "notion-poll.yml",
"name": "铸渊 · Notion 工单轮询",
@@ -281,6 +322,20 @@
"manual"
]
},
+ {
+ "file": "persona-invoke.yml",
+ "name": "Persona Invoke Endpoint",
+ "triggers": [
+ "manual"
+ ]
+ },
+ {
+ "file": "pm2-server-diagnose.yml",
+ "name": "\"🔧 铸渊 · PM2 服务诊断与健康检查\"",
+ "triggers": [
+ "manual"
+ ]
+ },
{
"file": "process-notion-orders.yml",
"name": "Process Notion Work Orders",
@@ -371,7 +426,7 @@
},
{
"file": "syslog-issue-pipeline.yml",
- "name": "SYSLOG Issue Pipeline",
+ "name": "📡 SYSLOG Issue Pipeline",
"triggers": [
"issues"
]
@@ -449,7 +504,7 @@
]
}
],
- "item_count": 37
+ "item_count": 44
},
{
"zone_id": "SCRIPTS",
@@ -478,6 +533,9 @@
{
"file": "contract-check.js"
},
+ {
+ "file": "create-standardized-ticket.js"
+ },
{
"file": "cross-repo-sync.js"
},
@@ -490,6 +548,12 @@
{
"file": "esp-email-processor.js"
},
+ {
+ "file": "generate-automation-map.js"
+ },
+ {
+ "file": "generate-communication-map.js"
+ },
{
"file": "generate-module-doc.js"
},
@@ -499,6 +563,15 @@
{
"file": "generate-session-summary.js"
},
+ {
+ "file": "generate-system-health.js"
+ },
+ {
+ "file": "intent-router.js"
+ },
+ {
+ "file": "invoke-persona.js"
+ },
{
"file": "notify-module-received.js"
},
@@ -508,9 +581,15 @@
{
"file": "notion-connectivity-test.js"
},
+ {
+ "file": "notion-heartbeat.js"
+ },
{
"file": "notion-signal-bridge.js"
},
+ {
+ "file": "pipeline-reporter.js"
+ },
{
"file": "process-broadcasts.js"
},
@@ -520,6 +599,9 @@
{
"file": "psp-inspection.js"
},
+ {
+ "file": "push-broadcast-to-github.js"
+ },
{
"file": "push-broadcast.js"
},
@@ -538,6 +620,9 @@
{
"file": "send-feishu-alert.js"
},
+ {
+ "file": "server-diagnose-report.js"
+ },
{
"file": "sync-login-entry.js"
},
@@ -550,6 +635,12 @@
{
"file": "update-readme-bulletin.js"
},
+ {
+ "file": "utils"
+ },
+ {
+ "file": "verify-modules.js"
+ },
{
"file": "wake-persona.js"
},
@@ -566,7 +657,7 @@
"file": "zhuyuan-module-protocol.js"
}
],
- "item_count": 33
+ "item_count": 45
},
{
"zone_id": "SRC",
@@ -930,14 +1021,26 @@
{
"file": "ai-system-doc.md"
},
+ {
+ "file": "execution-layer-map.md"
+ },
+ {
+ "file": "execution-status.md"
+ },
{
"file": "index.html"
},
+ {
+ "file": "notion-bridge-map.md"
+ },
+ {
+ "file": "repo-structure-map.md"
+ },
{
"file": "使用指南.md"
}
],
- "item_count": 6
+ "item_count": 10
},
{
"zone_id": "TESTS",
@@ -1198,6 +1301,9 @@
"bingshuo-system-health": [
"BRAIN::bingshuo-system-health.json"
],
+ "bulletin-board-today": [
+ "BRAIN::bulletin-board-today.json"
+ ],
"collaborators": [
"BRAIN::collaborators.json"
],
@@ -1310,6 +1416,9 @@
"check-structure": [
"WORKFLOWS::check-structure.yml"
],
+ "daily-maintenance": [
+ "WORKFLOWS::daily-maintenance.yml"
+ ],
"deploy-pages": [
"WORKFLOWS::deploy-pages.yml"
],
@@ -1323,6 +1432,12 @@
"esp-signal-processor": [
"WORKFLOWS::esp-signal-processor.yml"
],
+ "execution-sync": [
+ "WORKFLOWS::execution-sync.yml"
+ ],
+ "feishu-syslog-bridge": [
+ "WORKFLOWS::feishu-syslog-bridge.yml"
+ ],
"generate-module-doc": [
"WORKFLOWS::generate-module-doc.yml",
"SCRIPTS::generate-module-doc.js"
@@ -1330,13 +1445,26 @@
"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"
+ ],
+ "pm2-server-diagnose": [
+ "WORKFLOWS::pm2-server-diagnose.yml"
+ ],
"process-notion-orders": [
"WORKFLOWS::process-notion-orders.yml"
],
@@ -1438,6 +1566,9 @@
"contract-check": [
"SCRIPTS::contract-check.js"
],
+ "create-standardized-ticket": [
+ "SCRIPTS::create-standardized-ticket.js"
+ ],
"cross-repo-sync": [
"SCRIPTS::cross-repo-sync.js"
],
@@ -1447,12 +1578,27 @@
"esp-email-processor": [
"SCRIPTS::esp-email-processor.js"
],
+ "generate-automation-map": [
+ "SCRIPTS::generate-automation-map.js"
+ ],
+ "generate-communication-map": [
+ "SCRIPTS::generate-communication-map.js"
+ ],
"generate-repo-map": [
"SCRIPTS::generate-repo-map.js"
],
"generate-session-summary": [
"SCRIPTS::generate-session-summary.js"
],
+ "generate-system-health": [
+ "SCRIPTS::generate-system-health.js"
+ ],
+ "intent-router": [
+ "SCRIPTS::intent-router.js"
+ ],
+ "invoke-persona": [
+ "SCRIPTS::invoke-persona.js"
+ ],
"notify-module-received": [
"SCRIPTS::notify-module-received.js"
],
@@ -1462,6 +1608,9 @@
"notion-signal-bridge": [
"SCRIPTS::notion-signal-bridge.js"
],
+ "pipeline-reporter": [
+ "SCRIPTS::pipeline-reporter.js"
+ ],
"process-broadcasts": [
"SCRIPTS::process-broadcasts.js"
],
@@ -1471,6 +1620,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"
],
@@ -1483,12 +1635,21 @@
"send-feishu-alert": [
"SCRIPTS::send-feishu-alert.js"
],
+ "server-diagnose-report": [
+ "SCRIPTS::server-diagnose-report.js"
+ ],
"update-brain": [
"SCRIPTS::update-brain.js"
],
"update-memory": [
"SCRIPTS::update-memory.js"
],
+ "utils": [
+ "SCRIPTS::utils"
+ ],
+ "verify-modules": [
+ "SCRIPTS::verify-modules.js"
+ ],
"wake-persona": [
"SCRIPTS::wake-persona.js"
],
@@ -1751,6 +1912,18 @@
"ai-system-doc": [
"DOCS::ai-system-doc.md"
],
+ "execution-layer-map": [
+ "DOCS::execution-layer-map.md"
+ ],
+ "execution-status": [
+ "DOCS::execution-status.md"
+ ],
+ "notion-bridge-map": [
+ "DOCS::notion-bridge-map.md"
+ ],
+ "repo-structure-map": [
+ "DOCS::repo-structure-map.md"
+ ],
"使用指南": [
"DOCS::使用指南.md"
],
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 4fd16315..7c76b389 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-14 21:47 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -9,18 +9,18 @@
|------|------|
| 区域总数 | 13 个区域 |
| 功能模块 | 10 个 (m01~m18) |
-| 工作流 | 37 个 GitHub Actions |
-| 脚本 | 33 个执行脚本 |
+| 工作流 | 44 个 GitHub Actions |
+| 脚本 | 45 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-13 14:03 CST |
+| 快照生成时间 | 2026-03-14 21:47 CST |
---
## 🗺️ 图书馆全区地图
### 🧠 铸渊大脑(BRAIN)
-**路径**: `.github/brain` · **数量**: 18 项
+**路径**: `.github/brain` · **数量**: 19 项
**描述**: 铸渊核心记忆 · 路由映射 · 唤醒协议 · 图书馆目录
**关键词**: brain · memory · routing · wake · 大脑 · 记忆
@@ -30,12 +30,12 @@
**关键词**: persona · identity · dev-status · 人格 · 开发者状态
### ⚡ 自动化工作流(WORKFLOWS)
-**路径**: `.github/workflows` · **数量**: 37 项
+**路径**: `.github/workflows` · **数量**: 44 项
**描述**: 所有 GitHub Actions 工作流定义
**关键词**: workflow · actions · ci · automation · 工作流 · 自动化
### 🔧 执行脚本库(SCRIPTS)
-**路径**: `scripts` · **数量**: 33 项
+**路径**: `scripts` · **数量**: 45 项
**描述**: 铸渊所有执行手脚 · 自动化脚本
**关键词**: script · node · js · 脚本 · 执行 · runner
@@ -75,7 +75,7 @@
**关键词**: syslog · inbox · log · 系统日志
### 📄 文档与前端(DOCS)
-**路径**: `docs` · **数量**: 6 项
+**路径**: `docs` · **数量**: 10 项
**描述**: 铸渊助手聊天界面 · GitHub Pages 部署
**关键词**: docs · html · chat · pages · 文档 · 聊天室
@@ -97,14 +97,21 @@
| `bridge-session-summary.yml` | Generate Session Summary for Notion | schedule(50 23 * * *), manual |
| `bridge-syslog-to-notion.yml` | 铸渊 · Bridge A · SYSLOG → Notion | push, manual |
| `check-structure.yml` | 模块结构检查 | push, pull_request |
+| `daily-maintenance.yml` | "🔧 铸渊 · Daily Maintenance Agent" | schedule(0 2 * * *), manual |
| `deploy-pages.yml` | 🌀 部署铸渊聊天室 (GitHub Pages) | push, manual |
| `deploy-to-server.yml` | "🚀 铸渊 CD · 自动部署到 guanghulab.com" | push, manual |
| `distribute-broadcasts.yml` | 铸渊 · 广播分发 | push, manual |
| `esp-signal-processor.yml` | 铸渊 · ESP 邮件信号处理器(已暂停) | schedule(*/30 * * * *), manual |
+| `execution-sync.yml` | "📡 铸渊 · 执行层状态同步" | schedule(0 3 * * *), manual |
+| `feishu-syslog-bridge.yml` | 铸渊 · 飞书SYSLOG桥接 | unknown |
| `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 |
+| `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 |
@@ -117,7 +124,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 |
@@ -136,30 +143,42 @@
- `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`
- `scripts/esp-email-processor.js`
+- `scripts/generate-automation-map.js`
+- `scripts/generate-communication-map.js`
- `scripts/generate-module-doc.js`
- `scripts/generate-repo-map.js`
- `scripts/generate-session-summary.js`
+- `scripts/generate-system-health.js`
+- `scripts/intent-router.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/pipeline-reporter.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`
- `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`
- `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`
@@ -242,6 +261,7 @@
- `.github/brain/bingshuo-read-order.md`
- `.github/brain/bingshuo-routing-index.json`
- `.github/brain/bingshuo-system-health.json`
+- `.github/brain/bulletin-board-today.json`
- `.github/brain/collaborators.json`
- `.github/brain/growth-log.md`
- `.github/brain/human-registry.json`
@@ -257,9 +277,9 @@
## 🕐 最近动态(memory.json 最新3条)
+- `2026-03-14T08:48:40.855Z` · daily_check — passed
+- `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
---
diff --git a/.github/persona-brain/memory.json b/.github/persona-brain/memory.json
index dae0f307..8a14fefa 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",
@@ -41,7 +47,7 @@
"last_broadcast_received": "2026-03-10T13:24:33.774Z",
"active_rules_version": "v1.2",
"daily_selfcheck": {
- "last_run": "2026-03-13T00:56:41.683Z",
+ "last_run": "2026-03-14T00:56:03.800Z",
"brain_integrity": "ok",
"schema_coverage": "3/17",
"pending_broadcasts": 1
diff --git a/.github/workflows/daily-maintenance.yml b/.github/workflows/daily-maintenance.yml
new file mode 100644
index 00000000..4272cba3
--- /dev/null
+++ b/.github/workflows/daily-maintenance.yml
@@ -0,0 +1,248 @@
+name: "🔧 铸渊 · Daily Maintenance Agent"
+
+on:
+ schedule:
+ - cron: '0 2 * * *' # 每日 UTC 02:00 (北京时间 10:00)
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ maintenance:
+ name: 每日巡检与系统健康更新
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ # ── Step 0: 清空公告栏缓存(每日重置) ──
+ - name: 清空公告栏缓存
+ run: |
+ CACHE_FILE=".github/brain/bulletin-board-today.json"
+ TODAY=$(TZ="Asia/Shanghai" date +%Y-%m-%d)
+ if [ -f "$CACHE_FILE" ]; then
+ CACHE_DATE=$(jq -r '.date // ""' "$CACHE_FILE" 2>/dev/null || echo "")
+ if [ "$CACHE_DATE" != "$TODAY" ]; then
+ echo "{\"date\":\"$TODAY\",\"records\":[]}" > "$CACHE_FILE"
+ echo "✅ 公告栏缓存已清空(旧日期: $CACHE_DATE)"
+ else
+ echo "📌 公告栏缓存日期匹配今天 ($TODAY),无需清空"
+ fi
+ else
+ echo "{\"date\":\"$TODAY\",\"records\":[]}" > "$CACHE_FILE"
+ echo "✅ 创建新的公告栏缓存文件"
+ fi
+
+ # ── Step 1: 检查 brain/ 目录完整性 ──
+ - name: 检查 brain/ 目录完整性
+ id: brain_check
+ run: |
+ echo "🔍 检查 brain/ 目录..."
+ REQUIRED_FILES="master-brain.md read-order.md repo-map.json automation-map.json communication-map.json id-map.json system-health.json"
+ MISSING=""
+ for f in $REQUIRED_FILES; do
+ if [ ! -f "brain/$f" ]; then
+ MISSING="$MISSING $f"
+ echo "⚠️ 缺失: brain/$f"
+ else
+ echo "✅ 存在: brain/$f"
+ fi
+ done
+ if [ -z "$MISSING" ]; then
+ echo "brain_status=complete" >> "$GITHUB_OUTPUT"
+ echo "✅ brain/ 目录完整"
+ else
+ echo "brain_status=incomplete" >> "$GITHUB_OUTPUT"
+ echo "⚠️ 缺失文件:$MISSING"
+ fi
+
+ # ── Step 2: 检查最近 workflow 运行状态 ──
+ - name: 检查最近 workflow 运行状态
+ id: workflow_check
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "🔍 检查最近 workflow 运行..."
+ FAILED=$(gh api repos/${{ github.repository }}/actions/runs \
+ --jq '[.workflow_runs[] | select(.conclusion == "failure")] | length' \
+ 2>/dev/null || echo "0")
+ echo "failed_count=$FAILED" >> "$GITHUB_OUTPUT"
+ if [ "$FAILED" -gt "0" ]; then
+ echo "⚠️ 最近有 $FAILED 个失败的 workflow"
+ else
+ echo "✅ 最近没有失败的 workflow"
+ fi
+
+ # ── Step 3: 检查 Notion API 连通性 ──
+ - name: 检查 Notion API 连通性
+ id: notion_check
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
+ run: |
+ echo "🔍 检查 Notion API 连通性..."
+ if [ -z "$NOTION_TOKEN" ]; then
+ echo "notion_status=no_token" >> "$GITHUB_OUTPUT"
+ echo "⚠️ NOTION_TOKEN 未配置"
+ else
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
+ -H "Authorization: Bearer $NOTION_TOKEN" \
+ -H "Notion-Version: 2022-06-28" \
+ https://api.notion.com/v1/users/me 2>/dev/null || echo "000")
+ echo "notion_http_code=$HTTP_CODE" >> "$GITHUB_OUTPUT"
+ if [ "$HTTP_CODE" = "200" ]; then
+ echo "notion_status=connected" >> "$GITHUB_OUTPUT"
+ echo "✅ Notion API 连通"
+ else
+ echo "notion_status=error" >> "$GITHUB_OUTPUT"
+ echo "⚠️ Notion API 返回 HTTP $HTTP_CODE"
+ fi
+ fi
+
+ # ── Step 4: 更新 system-health.json ──
+ - name: 更新 system-health.json
+ run: node scripts/generate-system-health.js
+
+ # ── Step 5: 提交更新 ──
+ - name: 提交巡检结果
+ run: |
+ git config user.name "铸渊 Maintenance Agent"
+ git config user.email "actions@guanghulab.com"
+ git add brain/system-health.json .github/brain/bulletin-board-today.json
+ if git diff --cached --quiet; then
+ echo "📌 无变更需要提交"
+ else
+ git commit -m "🔧 Daily Maintenance: 更新 system-health.json [skip ci]"
+ git push
+ echo "✅ 已提交 system-health.json 更新"
+ fi
+
+ # ── Step 6: 向 Notion 回报巡检结果 ──
+ - name: 向 Notion 回报巡检结果
+ if: ${{ secrets.NOTION_TOKEN != '' && secrets.SYSLOG_DB_ID != '' }}
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
+ SYSLOG_DB_ID: ${{ secrets.SYSLOG_DB_ID }}
+ run: |
+ echo "📡 向 Notion 跨平台信号日志回报..."
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
+ BRAIN_STATUS="${{ steps.brain_check.outputs.brain_status }}"
+ FAILED_COUNT="${{ steps.workflow_check.outputs.failed_count }}"
+ NOTION_STATUS="${{ steps.notion_check.outputs.notion_status }}"
+
+ REPORT="Daily Maintenance Report | brain: ${BRAIN_STATUS} | failed_workflows: ${FAILED_COUNT} | notion: ${NOTION_STATUS}"
+
+ curl -s -X POST "https://api.notion.com/v1/pages" \
+ -H "Authorization: Bearer $NOTION_TOKEN" \
+ -H "Content-Type: application/json" \
+ -H "Notion-Version: 2022-06-28" \
+ -d "{
+ \"parent\": { \"database_id\": \"$SYSLOG_DB_ID\" },
+ \"properties\": {
+ \"标题\": {
+ \"title\": [{ \"text\": { \"content\": \"Daily Maintenance · $(date +%Y-%m-%d)\" } }]
+ },
+ \"状态\": { \"select\": { \"name\": \"已处理\" } },
+ \"类型\": { \"select\": { \"name\": \"巡检\" } }
+ },
+ \"children\": [{
+ \"object\": \"block\",
+ \"type\": \"paragraph\",
+ \"paragraph\": {
+ \"rich_text\": [{ \"text\": { \"content\": \"$REPORT\" } }]
+ }
+ }]
+ }" 2>&1 | tail -1
+ echo "✅ Notion 回报请求已发送"
+
+ # ── 同步执行层状态到 Notion Execution Status 数据库 ──
+ sync-execution-status-to-notion:
+ name: 同步执行层状态到 Notion
+ needs: maintenance
+ runs-on: ubuntu-latest
+ if: ${{ !cancelled() }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.ref }}
+
+ - name: Read brain data and sync to Notion
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
+ run: |
+ if [ -z "$NOTION_TOKEN" ]; then
+ echo "⚠️ NOTION_TOKEN 未配置,跳过 Execution Status 同步"
+ exit 0
+ fi
+
+ HEALTH=$(cat brain/system-health.json 2>/dev/null)
+ if [ -z "$HEALTH" ]; then
+ echo "⚠️ brain/system-health.json 未找到,使用默认值"
+ HEALTH='{"version":"unknown","system_health":"unknown"}'
+ fi
+ AUTO=$(cat brain/automation-map.json 2>/dev/null)
+ if [ -z "$AUTO" ]; then
+ echo "⚠️ brain/automation-map.json 未找到,使用默认值"
+ AUTO='{"workflows":[]}'
+ fi
+ REPO=$(cat brain/repo-map.json 2>/dev/null)
+ if [ -z "$REPO" ]; then
+ echo "⚠️ brain/repo-map.json 未找到,使用默认值"
+ REPO='{"directories":{}}'
+ fi
+
+ VERSION=$(echo "$HEALTH" | jq -r '.version // "unknown"')
+ STATUS=$(echo "$HEALTH" | jq -r '.system_health // "unknown"')
+ WF_COUNT=$(echo "$AUTO" | jq '[.workflows // [] | length] | .[0]' 2>/dev/null || echo '0')
+ CRON_COUNT=$(echo "$AUTO" | jq '[.workflows // [] | .[] | select(.cron)] | length' 2>/dev/null || echo '0')
+ DIR_COUNT=$(echo "$REPO" | jq '.directories // {} | keys | length' 2>/dev/null || echo '0')
+
+ case "$STATUS" in
+ normal) AUTO_S="🟢 Normal"; QUEUE_S="🟢 Active" ;;
+ partial) AUTO_S="🟡 Partial"; QUEUE_S="🟡 Backlog" ;;
+ *) AUTO_S="🔴 Down"; QUEUE_S="🔴 Blocked" ;;
+ esac
+
+ SYNC_DATE=$(date -u +%Y%m%d)
+ SYNC_TS=$(date -u +%Y-%m-%dT%H:%M:%S+00:00)
+
+ echo "📡 同步 Execution Status → Notion..."
+ echo " 版本: v${VERSION} · ${DIR_COUNT}+ dirs"
+ echo " 模块: ${WF_COUNT} workflow · ${CRON_COUNT} cron"
+ echo " 状态: ${AUTO_S}"
+
+ HTTP_CODE=$(curl -s -o /tmp/notion_resp.json -w "%{http_code}" \
+ -X POST 'https://api.notion.com/v1/pages' \
+ -H "Authorization: Bearer $NOTION_TOKEN" \
+ -H "Notion-Version: 2022-06-28" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"parent\": { \"database_id\": \"41971982-4a17-47c0-afe3-21458c44f154\" },
+ \"properties\": {
+ \"同步记录\": { \"title\": [{ \"text\": { \"content\": \"SYNC-${SYNC_DATE}-001\" } }] },
+ \"同步时间\": { \"date\": { \"start\": \"${SYNC_TS}\" } },
+ \"仓库版本\": { \"rich_text\": [{ \"text\": { \"content\": \"v${VERSION} · ${DIR_COUNT}+ dirs\" } }] },
+ \"执行模块\": { \"rich_text\": [{ \"text\": { \"content\": \"${WF_COUNT} workflow · ${CRON_COUNT} cron\" } }] },
+ \"自动化状态\": { \"select\": { \"name\": \"${AUTO_S}\" } },
+ \"Notion连接状态\": { \"select\": { \"name\": \"🟢 Connected\" } },
+ \"任务队列状态\": { \"select\": { \"name\": \"${QUEUE_S}\" } },
+ \"同步来源\": { \"select\": { \"name\": \"铸渊自动同步\" } },
+ \"备注\": { \"rich_text\": [{ \"text\": { \"content\": \"Daily Maintenance auto sync\" } }] }
+ }
+ }")
+
+ if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
+ echo "✅ Execution Status synced to Notion (HTTP ${HTTP_CODE})"
+ else
+ echo "❌ Sync failed (HTTP ${HTTP_CODE})"
+ cat /tmp/notion_resp.json 2>/dev/null || true
+ exit 1
+ fi
diff --git a/.github/workflows/deploy-to-server.yml b/.github/workflows/deploy-to-server.yml
index fa73dcf1..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
@@ -378,8 +379,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 +415,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/.github/workflows/execution-sync.yml b/.github/workflows/execution-sync.yml
new file mode 100644
index 00000000..0d7053c9
--- /dev/null
+++ b/.github/workflows/execution-sync.yml
@@ -0,0 +1,57 @@
+name: "📡 铸渊 · 执行层状态同步"
+
+on:
+ schedule:
+ - cron: '0 3 * * *' # 每日 UTC 03:00 (北京时间 11:00)
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ execution-sync:
+ name: 执行层状态采集与同步
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ # ── Step 1: 加载执行上下文 ──
+ - name: 加载执行上下文
+ run: node core/context-loader
+
+ # ── Step 2: 运行系统自检 ──
+ - name: 运行系统自检
+ run: node core/system-check --auto-tasks
+
+ # ── Step 3: 生成执行状态报告 ──
+ - name: 生成执行状态报告
+ run: node core/execution-sync report
+
+ # ── Step 4: 同步到 Notion ──
+ - name: 同步执行状态到 Notion
+ if: ${{ env.NOTION_TOKEN != '' }}
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
+ EXECUTION_LOG_DB_ID: ${{ secrets.EXECUTION_LOG_DB_ID }}
+ run: node core/execution-sync sync
+
+ # ── Step 5: 提交状态报告 ──
+ - name: 提交状态报告
+ run: |
+ git config user.name "铸渊 Execution Sync"
+ git config user.email "actions@guanghulab.com"
+ git add docs/execution-status.md
+ if git diff --cached --quiet; then
+ echo "📌 无变更需要提交"
+ else
+ git commit -m "📡 Execution Sync: 更新执行状态报告 [skip ci]"
+ git push
+ echo "✅ 已提交执行状态报告"
+ fi
diff --git a/.github/workflows/feishu-syslog-bridge.yml b/.github/workflows/feishu-syslog-bridge.yml
new file mode 100644
index 00000000..e460275d
--- /dev/null
+++ b/.github/workflows/feishu-syslog-bridge.yml
@@ -0,0 +1,46 @@
+name: 铸渊 · 飞书SYSLOG桥接
+
+on:
+ repository_dispatch:
+ types: [feishu-syslog]
+
+jobs:
+ bridge-feishu-syslog:
+ name: 📡 飞书SYSLOG → syslog-inbox
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ ref: main
+
+ - name: 📥 写入 syslog-inbox
+ env:
+ SYSLOG_RAW: ${{ toJson(github.event.client_payload) }}
+ run: |
+ mkdir -p syslog-inbox
+ TIMESTAMP=$(date -u +%Y%m%d-%H%M%S)
+ RAW_SOURCE=$( echo "$SYSLOG_RAW" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('source','unknown'))" 2>/dev/null || echo 'feishu' )
+ SOURCE=$( echo "$RAW_SOURCE" | tr -cd 'a-zA-Z0-9_-' | head -c 64 )
+ SOURCE=${SOURCE:-feishu}
+ FILENAME="syslog-inbox/${TIMESTAMP}_${SOURCE}.json"
+ echo "$SYSLOG_RAW" > "$FILENAME"
+ echo "📥 SYSLOG已写入: $FILENAME"
+ echo "📋 内容预览:"
+ cat "$FILENAME" | head -20
+
+ - name: 提交到仓库
+ run: |
+ git config user.name "铸渊[bot]"
+ git config user.email "zhu-yuan-bot@guanghulab.com"
+ git add syslog-inbox/
+ git diff --staged --quiet || git commit -m "📡 feishu-syslog: 飞书舒舒转发SYSLOG [auto-bridge]"
+ git push
+
+ - name: ✅ 桥接完成
+ run: |
+ echo "✅ 飞书SYSLOG已桥接到 syslog-inbox/"
+ echo "📡 等待 syslog-pipeline 自动触发处理"
diff --git a/.github/workflows/notion-callback-pipeline.yml b/.github/workflows/notion-callback-pipeline.yml
new file mode 100644
index 00000000..463d7ced
--- /dev/null
+++ b/.github/workflows/notion-callback-pipeline.yml
@@ -0,0 +1,317 @@
+name: Notion Callback Pipeline
+# 📥 Notion → GitHub 回传闭环管道
+#
+# 当 Notion 侧核心大脑人格体处理完 SYSLOG 工单后,
+# 通过 repository_dispatch 回传处理结果到 GitHub。
+# 铸渊 Agent 接收回传,重新唤醒核心大脑认知进行二次核验,
+# 确认新广播生成无误后,自动发送邮件通知开发者。
+#
+# 触发方式:
+# Notion 侧 Agent 调用 GitHub API 发送 repository_dispatch 事件
+# event_type: notion-syslog-callback
+# client_payload: {
+# broadcast_id: "BC-XXX-XXX-XX",
+# dev_email: "developer@email.com",
+# notion_result: "Notion 侧处理结果文本",
+# ticket_id: "Notion 工单 ID",
+# status: "approved" | "needs_revision"
+# }
+#
+# 闭环流程:
+# ① Notion 侧处理完成 → repository_dispatch 回传
+# ② 铸渊 Agent 接收并解析回传数据
+# ③ 重新唤醒铸渊核心大脑认知(二次核验)
+# ④ 核心大脑核对广播生成无误
+# ⑤ 铸渊 Agent 发送邮件通知开发者
+# ⑥ 闭环结束
+#
+# 依赖 Secrets:
+# LLM_API_KEY 第三方 LLM 平台密钥
+# LLM_BASE_URL 第三方 LLM 平台 API 地址
+# NOTION_API_TOKEN Notion API token
+# CORE_BRAIN_PAGE_ID 曜冥核心大脑 v4.0 页面 ID
+# PORTRAIT_DB_ID 开发者动态画像库数据库 ID
+# FINGERPRINT_DB_ID 模块指纹注册表数据库 ID
+# SMTP_USER QQ 邮箱地址
+# SMTP_PASS QQ 邮箱 SMTP 授权码
+
+on:
+ repository_dispatch:
+ types: [notion-syslog-callback]
+
+jobs:
+ verify-and-notify:
+ name: 📥 Notion 回传 · 二次核验 · 邮件通知
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: 📥 Parse Notion callback
+ id: callback
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const payload = context.payload.client_payload || {};
+
+ const broadcastId = payload.broadcast_id || '';
+ const devEmail = payload.dev_email || '';
+ const notionResult = payload.notion_result || '';
+ const ticketId = payload.ticket_id || '';
+ const status = payload.status || 'unknown';
+
+ if (!broadcastId) {
+ core.setFailed('❌ 缺少 broadcast_id');
+ return;
+ }
+
+ core.setOutput('broadcast_id', broadcastId);
+ core.setOutput('dev_email', devEmail);
+ core.setOutput('notion_result', notionResult);
+ core.setOutput('ticket_id', ticketId);
+ core.setOutput('status', status);
+
+ console.log(`📥 Notion 回传: 广播=${broadcastId}, 状态=${status}, 工单=${ticketId}`);
+
+ - name: 🔍 铸渊 Agent · 模块二次验证
+ id: verify
+ env:
+ SYSLOG_CONTENT: ${{ steps.callback.outputs.notion_result }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ AUTHOR: notion-callback
+ run: node scripts/verify-modules.js
+
+ - name: 🧠 二次核验 · 唤醒铸渊核心大脑
+ id: persona
+ env:
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+ LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ SUBMIT_TYPE: syslog
+ SUBMIT_CONTENT: ${{ steps.callback.outputs.notion_result }}
+ AUTHOR: notion-callback
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
+ NOTION_CALLBACK_RESULT: ${{ steps.callback.outputs.notion_result }}
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
+ PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
+ FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
+ run: node scripts/wake-persona.js
+
+ - name: 📡 推送广播到 GitHub(Phase B4)
+ if: steps.persona.outcome == 'success' && steps.callback.outputs.status == 'approved'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ DEVELOPER_NAME: notion-callback
+ BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
+ run: node scripts/push-broadcast-to-github.js
+
+ - name: 📝 回写 Notion 工单状态
+ if: steps.callback.outputs.ticket_id
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ TICKET_ID: ${{ steps.callback.outputs.ticket_id }}
+ CALLBACK_STATUS: ${{ steps.callback.outputs.status }}
+ run: |
+ node -e "
+ var https = require('https');
+ var token = process.env.NOTION_TOKEN || '';
+ var ticketId = process.env.TICKET_ID || '';
+ var status = process.env.CALLBACK_STATUS || 'unknown';
+
+ if (!token || !ticketId) {
+ console.log('⚠️ 跳过工单状态更新');
+ process.exit(0);
+ }
+
+ var notionStatus = status === 'approved' ? '✅ 已完成' : '⚠️ 异常·等人工介入';
+ var body = JSON.stringify({
+ properties: {
+ '状态': { select: { name: notionStatus } }
+ }
+ });
+
+ var opts = {
+ hostname: 'api.notion.com',
+ port: 443,
+ path: '/v1/pages/' + ticketId,
+ method: 'PATCH',
+ headers: {
+ 'Authorization': 'Bearer ' + token,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': '2022-06-28',
+ 'Content-Length': Buffer.byteLength(body)
+ }
+ };
+
+ var req = https.request(opts, function(res) {
+ var data = '';
+ res.on('data', function(c) { data += c; });
+ res.on('end', function() {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ console.log('✅ 工单状态已更新为 ' + notionStatus);
+ } else {
+ console.log('⚠️ 工单更新失败: ' + res.statusCode);
+ }
+ });
+ });
+ req.on('error', function(e) { console.log('⚠️ ' + e.message); });
+ req.write(body);
+ req.end();
+ "
+
+ - name: 📧 发送最终确认邮件
+ env:
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ EMAIL_TO: ${{ steps.callback.outputs.dev_email }}
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ NOTION_STATUS: ${{ steps.callback.outputs.status }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ run: |
+ node -e "
+ const nodemailer = require('nodemailer');
+
+ const user = process.env.SMTP_USER || '';
+ const pass = process.env.SMTP_PASS || '';
+ const to = process.env.EMAIL_TO || '';
+ const broadcastId = process.env.BROADCAST_ID || '';
+ const notionStatus = process.env.NOTION_STATUS || 'unknown';
+ const result = process.env.PERSONA_RESULT || '(处理中)';
+
+ if (!user || !pass) {
+ console.log('⚠️ SMTP not configured, skipping email');
+ process.exit(0);
+ }
+ if (!to) {
+ console.log('⚠️ No recipient email, skipping');
+ process.exit(0);
+ }
+
+ const isApproved = notionStatus === 'approved';
+ const subjectText = isApproved
+ ? '[光湖系统] ' + broadcastId + ' · ✅ 新广播已生成(已核验)'
+ : '[光湖系统] ' + broadcastId + ' · ⚠️ 广播需修订';
+
+ const statusBadge = isApproved
+ ? '✅ 已核验通过'
+ : '⚠️ 需修订';
+
+ 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();
+
+ // 检查是否已存在相同事件(按 broadcast_id 精确匹配 + 同类型 + 同日期)
+ const eventKey = 'Notion 回传核验 · ' + broadcastId;
+ const todayStr = now.slice(0, 10);
+ const existingIdx = memory.events.findIndex(function(e) {
+ return e.type === 'notion_callback' &&
+ e.broadcast_id === broadcastId &&
+ e.date === todayStr;
+ });
+
+ const newEvent = {
+ date: todayStr,
+ type: 'notion_callback',
+ broadcast_id: broadcastId,
+ description: eventKey + ' · 状态: ' + status,
+ by: '铸渊Agent·Notion回传管道'
+ };
+
+ if (existingIdx >= 0) {
+ // 同一 broadcast_id 只保留最新的一条
+ memory.events[existingIdx] = newEvent;
+ console.log('ℹ️ 已更新同日同广播事件');
+ } else {
+ memory.events.unshift(newEvent);
+ }
+
+ // 保留最近 20 条事件
+ if (memory.events.length > 20) {
+ memory.events = memory.events.slice(0, 20);
+ }
+
+ fs.writeFileSync(memoryPath, JSON.stringify(memory, null, 2) + '\n');
+ console.log('✅ 大脑记忆已更新');
+ "
+ env:
+ BROADCAST_ID: ${{ steps.callback.outputs.broadcast_id }}
+ NOTION_STATUS: ${{ steps.callback.outputs.status }}
+
+ - name: 📤 Commit memory update
+ run: |
+ git config user.name "zhuyuan-agent[bot]"
+ git config user.email "zhuyuan-agent[bot]@users.noreply.github.com"
+ git add .github/brain/memory.json
+ git diff --cached --quiet || git commit -m "🧠 Notion回传核验 · ${{ steps.callback.outputs.broadcast_id }}"
+ git push || echo "⚠️ Push skipped (no changes or conflict)"
diff --git a/.github/workflows/notion-heartbeat.yml b/.github/workflows/notion-heartbeat.yml
new file mode 100644
index 00000000..76653dc0
--- /dev/null
+++ b/.github/workflows/notion-heartbeat.yml
@@ -0,0 +1,47 @@
+name: Notion Heartbeat Monitor
+# 💓 Phase B3 · 工单心跳监控
+#
+# 每 5 分钟检测 Notion 工单队列中的待处理工单:
+# - 超过 5 分钟未回执 → 重新触发人格体唤醒(最多 3 次)
+# - 3 次重试仍无回执 → 标记工单异常 + 发邮件通知冰朔
+#
+# 依赖 Secrets:
+# NOTION_API_TOKEN Notion API token
+# NOTION_TICKET_DB_ID 工单队列数据库 ID
+# SMTP_USER 邮件发送者
+# SMTP_PASS 邮件授权码
+
+on:
+ schedule:
+ - cron: '*/5 * * * *'
+ workflow_dispatch:
+
+jobs:
+ heartbeat:
+ name: 💓 工单心跳检测
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ actions: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: 💓 执行心跳检测
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ ALERT_EMAIL: '565183519@qq.com'
+ run: node scripts/notion-heartbeat.js
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/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/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml
index c2fa3898..a0d84b10 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,13 +123,15 @@ jobs:
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
AUTHOR: ${{ steps.parse.outputs.author }}
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
- - name: 📋 Write to Notion ticket
+ - name: 📋 创建标准化 Notion 工单(Phase B1)
+ id: ticket
if: ${{ secrets.NOTION_API_TOKEN }}
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
@@ -119,70 +139,20 @@ jobs:
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
- run: |
- node -e "
- const https = require('https');
+ MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
+ MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
+ DEVELOPER: ${{ steps.parse.outputs.author }}
+ SYSLOG_RAW: ${{ steps.parse.outputs.content }}
+ run: node scripts/create-standardized-ticket.js
- const token = process.env.NOTION_TOKEN;
- const dbId = process.env.NOTION_TICKET_DB_ID;
- const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
- const type = process.env.SUBMIT_TYPE || 'syslog';
- const result = process.env.PERSONA_RESULT || '(no result)';
-
- if (!token || !dbId) {
- console.log('⚠️ Notion credentials not configured, skipping ticket creation');
- process.exit(0);
- }
-
- const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
- const title = '[自动] ' + broadcastId + ' · ' + typeLabel;
-
- const body = JSON.stringify({
- parent: { database_id: dbId },
- properties: {
- '标题': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
- '操作类型': { select: { name: '其他' } },
- '提交者': { rich_text: [{ type: 'text', text: { content: '自动管道' } }] },
- '状态': { select: { name: '已完成' } },
- '优先级': { select: { name: 'P1' } }
- },
- children: [{
- object: 'block',
- type: 'paragraph',
- paragraph: {
- rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
- }
- }]
- });
-
- const opts = {
- hostname: 'api.notion.com',
- port: 443,
- path: '/v1/pages',
- method: 'POST',
- headers: {
- 'Authorization': 'Bearer ' + token,
- 'Content-Type': 'application/json',
- 'Notion-Version': '2022-06-28',
- 'Content-Length': Buffer.byteLength(body)
- }
- };
-
- const req = https.request(opts, (res) => {
- let data = '';
- res.on('data', (c) => data += c);
- res.on('end', () => {
- if (res.statusCode >= 200 && res.statusCode < 300) {
- console.log('✅ Notion 工单已创建');
- } else {
- console.log('⚠️ Notion 工单创建失败: ' + res.statusCode + ' ' + data);
- }
- });
- });
- req.on('error', (e) => console.log('⚠️ Notion 请求失败: ' + e.message));
- req.write(body);
- req.end();
- "
+ - name: 📡 推送广播到 GitHub(Phase B4)
+ if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ DEVELOPER_NAME: ${{ steps.parse.outputs.author }}
+ BROADCAST_CONTENT: ${{ steps.persona.outputs.result }}
+ run: node scripts/push-broadcast-to-github.js
- name: 📧 Send email to developer
env:
@@ -266,11 +236,16 @@ jobs:
const type = '${{ steps.parse.outputs.type }}';
const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
const email = '${{ steps.parse.outputs.email }}';
+ const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true';
+ const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0';
const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答';
const maskedEmail = email.length > 4
? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
: '***';
+ const moduleStatus = moduleCount === '0'
+ ? 'ℹ️ 未检测到模块引用'
+ : (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传');
const body = [
'✅ **已处理** · ' + typeLabel,
@@ -278,13 +253,15 @@ jobs:
'| 项目 | 内容 |',
'|------|------|',
'| 📡 广播编号 | `' + broadcastId + '` |',
+ '| 🔍 模块验证 | ' + moduleStatus + ' |',
'| 📧 结果发送至 | `' + maskedEmail + '` |',
'| 🤖 处理人格体 | 铸渊 |',
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
'',
- '> 结果已发送到你的邮箱,请查收。',
+ '> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
+ '> 结果已发送到你的邮箱,Notion 侧工单已创建。',
'>',
- '> 如未收到,请检查垃圾箱或重新提交。'
+ '> 如未收到邮件,请检查垃圾箱或重新提交。'
].join('\n');
// Use GraphQL to add discussion comment
diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml
index 5a5d81a5..c7386478 100644
--- a/.github/workflows/syslog-issue-pipeline.yml
+++ b/.github/workflows/syslog-issue-pipeline.yml
@@ -1,9 +1,19 @@
-name: SYSLOG Issue Pipeline
+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 平台密钥(必须)
@@ -24,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
@@ -41,6 +56,16 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts
+ - 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
uses: actions/github-script@v7
@@ -96,6 +121,58 @@ 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:
+ 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: 📋 步骤 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:
@@ -105,13 +182,37 @@ jobs:
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
AUTHOR: ${{ steps.parse.outputs.author }}
+ MODULE_VERIFY_RESULT: ${{ steps.verify.outputs.verify_report }}
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
run: node scripts/wake-persona.js
- - name: 📋 Write to Notion ticket
+ - name: 📋 步骤 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 }}
env:
NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
@@ -119,72 +220,90 @@ jobs:
BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
PERSONA_RESULT: ${{ steps.persona.outputs.result }}
- run: |
- node -e "
- const https = require('https');
+ MODULE_VERIFY: ${{ steps.verify.outputs.verify_result }}
+ MODULES_UPLOADED: ${{ steps.verify.outputs.modules_uploaded }}
+ DEVELOPER: ${{ steps.parse.outputs.author }}
+ SYSLOG_RAW: ${{ steps.parse.outputs.content }}
+ run: node scripts/create-standardized-ticket.js
- const token = process.env.NOTION_TOKEN;
- const dbId = process.env.NOTION_TICKET_DB_ID;
- const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
- const type = process.env.SUBMIT_TYPE || 'syslog';
- const result = process.env.PERSONA_RESULT || '(no result)';
+ - 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
- if (!token || !dbId) {
- console.log('⚠️ Notion credentials not configured, skipping ticket creation');
- process.exit(0);
- }
+ - 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
- const typeLabel = type === 'syslog' ? 'SYSLOG闭环' : '提问解答';
- const title = '[自动] ' + broadcastId + ' · ' + typeLabel;
+ - 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
- const body = JSON.stringify({
- parent: { database_id: dbId },
- properties: {
- '标题': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
- '操作类型': { select: { name: '其他' } },
- '提交者': { rich_text: [{ type: 'text', text: { content: '自动管道' } }] },
- '状态': { select: { name: '已完成' } },
- '优先级': { select: { name: 'P1' } }
- },
- children: [{
- object: 'block',
- type: 'paragraph',
- paragraph: {
- rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
- }
- }]
- });
+ - name: 📡 推送广播到 GitHub(Phase B4)
+ id: broadcast
+ 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
- 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)
- }
- };
+ - 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
- 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: 📋 步骤 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 }}
@@ -258,6 +377,28 @@ jobs:
});
"
+ - name: 📋 步骤 7/8 · 邮件通知(成功)
+ if: always() && steps.email.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: 📋 步骤 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:
@@ -266,25 +407,34 @@ 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,
+ '## 🎉 闭环处理完成 · ' + typeLabel,
'',
'| 项目 | 内容 |',
'|------|------|',
'| 📡 广播编号 | `' + broadcastId + '` |',
+ '| 🔍 模块验证 | ' + moduleStatus + ' |',
'| 📧 结果发送至 | `' + maskedEmail + '` |',
'| 🤖 处理人格体 | 铸渊 |',
'| ⏰ 处理时间 | ' + new Date().toISOString() + ' |',
'',
- '> 结果已发送到你的邮箱,请查收。',
+ '> 铸渊核心大脑已完成 SYSLOG 验收 + 模块检测 + 广播生成。',
+ '> 结果已发送到你的邮箱,Notion 侧工单已创建。',
'>',
- '> 如未收到,请检查垃圾箱或重新提交。'
+ '> 如未收到邮件,请检查垃圾箱或重新提交。',
+ '>',
+ '> *—— 铸渊(ICE-GL-ZY001)· 代码守护人格体*'
].join('\n');
await github.rest.issues.createComment({
@@ -313,12 +463,14 @@ jobs:
const number = context.payload.issue?.number;
if (!number) return;
+ const actionsUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions';
+
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❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 部分步骤异常,已记录日志。冰朔会在下次巡检时处理。\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊(ICE-GL-ZY001)*'
});
} catch (err) {
console.log('⚠️ 失败告警回复失败: ' + err.message);
diff --git a/.github/workflows/update-readme-bulletin.yml b/.github/workflows/update-readme-bulletin.yml
index 6247d891..1571d635 100644
--- a/.github/workflows/update-readme-bulletin.yml
+++ b/.github/workflows/update-readme-bulletin.yml
@@ -77,7 +77,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add README.md
+ git add README.md .github/brain/bulletin-board-today.json
if git diff --cached --quiet; then
echo "📢 公告区无变化,跳过提交"
else
diff --git a/.github/workflows/zhuyuan-issue-reply.yml b/.github/workflows/zhuyuan-issue-reply.yml
index 6b713545..90b44c60 100644
--- a/.github/workflows/zhuyuan-issue-reply.yml
+++ b/.github/workflows/zhuyuan-issue-reply.yml
@@ -7,12 +7,82 @@ 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 自动管道已启动,预计 2-5 分钟完成全部处理。',
+ '每完成一步都会在下方更新进度评论,你刷新页面即可看到最新状态。',
+ '',
+ '> 如超过 10 分钟无后续回复,请在下方评论中 `@铸渊 查询#' + number + '闭环状态`。',
+ '>',
+ '> *—— 铸渊(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 新建或评论中包含 @铸渊 / 铸渊 时触发
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/.gitignore b/.gitignore
index a0e218d9..455e645e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,8 @@ modules/palace-game/data/saves/PAL-*
# collaboration-logs exports (generated by save-collaboration-log.js)
collaboration-logs/exports/
+
+# core runtime artifacts
+core/task-queue/queue.json
+core/task-queue/execution-log.json
+core/system-check/last-report.json
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小时后可正常搜索
+
diff --git a/README.md b/README.md
index 51988395..c8a950e0 100644
--- a/README.md
+++ b/README.md
@@ -92,21 +92,21 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-13 14:04 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
-| 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-14 21:49 | ⏹️ 📢 更新系统公告区 · cancelled | 冰朔 |
+| 03-14 21:48 | ✅ 🚀 铸渊 CD · 自动部署到 guanghulab.com · 成功 | 冰朔 |
+| 03-14 21:47 | ✅ Notion Heartbeat Monitor · 成功 | 冰朔 |
+| 03-14 21:47 | ✅ 📢 更新系统公告区 · 成功 | 冰朔 |
+| 03-14 21:47 | 🔧 系统更新: `.github/` | github-actions[bot] |
+| 03-14 21:47 | ✅ 铸渊 · Bridge E · GitHub Changes → Notion · 成功 | 冰朔 |
+| 03-14 16:48 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-14 16:18 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
+| 03-14 14:58 | 🔧 系统更新: `docs/` | 铸渊 (Copilot) |
+| 03-14 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 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巡检 |
-| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
-| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
### 🤖 铸渊自动提醒
@@ -114,7 +114,7 @@
> 🟢 **今日无需冰朔手动干预** · 系统一切正常
>
-> 🗓️ 2026-03-13 · 铸渊自动检测
+> 🗓️ 2026-03-14 · 铸渊自动检测
---
@@ -126,11 +126,14 @@
> 若需要手动干预,相关条目将标红并发送邮件提醒。
+👥 合作者公告栏(2026-03-14)
+
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-13 14:03 | 冰朔 | `—/` | ✅ 上传成功 |
-| 03-13 14:03 | Copilot | `—/` | ✅ 上传成功 |
-| 03-13 13:56 | Copilot | `—/` | 🔵 已更新 |
+| 16:25 | Copilot | `—/` | ✅ 上传成功 |
+| 16:25 | 冰朔 | `—/` | ✅ 上传成功 |
+| 21:47 | Copilot | `—/` | ✅ 上传成功 |
+| 21:47 | 冰朔 | `—/` | ✅ 上传成功 |
### 🤖 铸渊自动提醒 · 合作者
@@ -138,7 +141,7 @@
> 🟢 **今日无需合作者手动干预** · 所有模块状态正常
>
-> 🗓️ 2026-03-13 · 铸渊自动检测
+> 🗓️ 2026-03-14 · 铸渊自动检测
---
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/brain/automation-map.json b/brain/automation-map.json
new file mode 100644
index 00000000..5036c76a
--- /dev/null
+++ b/brain/automation-map.json
@@ -0,0 +1,650 @@
+{
+ "version": "4.0",
+ "generated_at": "2026-03-13T19:36:48.677Z",
+ "generated_by": "scripts/generate-automation-map.js",
+ "description": "数字地球系统自动化地图 · 所有工作流与自动化脚本",
+ "workflows": [
+ {
+ "file": "bingshuo-deploy-agent.yml",
+ "name": "🧊 冰朔人格体 · 自动部署诊断",
+ "triggers": [
+ "issues",
+ "issue_comment",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/bingshuo-deploy-agent.js"
+ ]
+ },
+ {
+ "file": "bingshuo-neural-system.yml",
+ "name": "冰朔主控神经系统 · 自动维护",
+ "triggers": [
+ "push",
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 0 * * *",
+ "scripts": [
+ "scripts/bingshuo-neural-sync.js"
+ ]
+ },
+ {
+ "file": "brain-sync.yml",
+ "name": "铸渊 Brain Sync",
+ "triggers": [
+ "push",
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 8 * * *",
+ "scripts": [
+ "scripts/process-broadcasts.js",
+ "scripts/daily-check.js",
+ "scripts/update-memory.js"
+ ]
+ },
+ {
+ "file": "bridge-changes-to-notion.yml",
+ "name": "铸渊 · Bridge E · GitHub Changes → Notion",
+ "triggers": [
+ "push",
+ "pull_request"
+ ],
+ "scripts": [
+ "scripts/notion-bridge.js",
+ "scripts/notion-bridge.js"
+ ]
+ },
+ {
+ "file": "bridge-session-summary.yml",
+ "name": "Generate Session Summary for Notion",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "50 23 * * *",
+ "scripts": [
+ "scripts/generate-session-summary.js"
+ ]
+ },
+ {
+ "file": "bridge-syslog-to-notion.yml",
+ "name": "铸渊 · Bridge A · SYSLOG → Notion",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/notion-bridge.js"
+ ]
+ },
+ {
+ "file": "check-structure.yml",
+ "name": "模块结构检查",
+ "triggers": [
+ "unknown"
+ ]
+ },
+ {
+ "file": "daily-maintenance.yml",
+ "name": "🔧 铸渊 · Daily Maintenance Agent",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 2 * * *",
+ "scripts": [
+ "scripts/generate-system-health.js"
+ ]
+ },
+ {
+ "file": "deploy-pages.yml",
+ "name": "🌀 部署铸渊聊天室 (GitHub Pages)",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "deploy-to-server.yml",
+ "name": "🚀 铸渊 CD · 自动部署到 guanghulab.com",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/contract-check.js",
+ "scripts/zhuyuan-module-protocol.js",
+ "scripts/notion-bridge.js"
+ ]
+ },
+ {
+ "file": "distribute-broadcasts.yml",
+ "name": "铸渊 · 广播分发",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/distribute-broadcasts.js"
+ ]
+ },
+ {
+ "file": "esp-signal-processor.yml",
+ "name": "铸渊 · ESP 邮件信号处理器(已暂停)",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "*/30 * * * *",
+ "scripts": [
+ "scripts/esp-email-processor.js"
+ ]
+ },
+ {
+ "file": "generate-module-doc.yml",
+ "name": "铸渊 · 光湖纪元 模块文档自动生成",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/generate-module-doc.js",
+ "scripts/notify-module-received.js"
+ ]
+ },
+ {
+ "file": "hli-contract-check.yml",
+ "name": "HLI Contract Check",
+ "triggers": [
+ "push",
+ "pull_request"
+ ]
+ },
+ {
+ "file": "notion-callback-pipeline.yml",
+ "name": "Notion Callback Pipeline",
+ "triggers": [
+ "unknown"
+ ],
+ "scripts": [
+ "scripts/verify-modules.js",
+ "scripts/wake-persona.js",
+ "scripts/push-broadcast-to-github.js"
+ ]
+ },
+ {
+ "file": "notion-connectivity-test.yml",
+ "name": "铸渊 · Notion 连通性测试",
+ "triggers": [
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/notion-connectivity-test.js"
+ ]
+ },
+ {
+ "file": "notion-heartbeat.yml",
+ "name": "Notion Heartbeat Monitor",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "*/5 * * * *",
+ "scripts": [
+ "scripts/notion-heartbeat.js"
+ ]
+ },
+ {
+ "file": "notion-poll.yml",
+ "name": "铸渊 · Notion 工单轮询",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "*/15 * * * *",
+ "scripts": [
+ "scripts/notion-signal-bridge.js"
+ ]
+ },
+ {
+ "file": "persona-invoke.yml",
+ "name": "Persona Invoke Endpoint",
+ "triggers": [
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/verify-modules.js",
+ "scripts/wake-persona.js",
+ "scripts/push-broadcast-to-github.js"
+ ]
+ },
+ {
+ "file": "pm2-server-diagnose.yml",
+ "name": "🔧 铸渊 · PM2 服务诊断与健康检查",
+ "triggers": [
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/server-diagnose-report.js"
+ ]
+ },
+ {
+ "file": "process-notion-orders.yml",
+ "name": "Process Notion Work Orders",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "ps-on-build.yml",
+ "name": "🌊 Persona Studio · 代码生成",
+ "triggers": [
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "ps-on-chat.yml",
+ "name": "🌊 Persona Studio · 对话处理",
+ "triggers": [
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "ps-on-complete.yml",
+ "name": "🌊 Persona Studio · 完成通知",
+ "triggers": [
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "ps-on-login.yml",
+ "name": "🌊 Persona Studio · 登录校验",
+ "triggers": [
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "psp-daily-inspection.yml",
+ "name": "铸渊 · PSP 分身巡检",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 1 * * *",
+ "scripts": [
+ "scripts/psp-inspection.js"
+ ]
+ },
+ {
+ "file": "push-broadcast.yml",
+ "name": "铸渊 · Push Broadcast · Notion → 飞书文档B",
+ "triggers": [
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/push-broadcast.js",
+ "scripts/send-feishu-alert.js",
+ "scripts/send-feishu-alert.js"
+ ]
+ },
+ {
+ "file": "receive-syslog.yml",
+ "name": "铸渊 · Receive SYSLOG · 飞书机器人 → GitHub → Notion",
+ "triggers": [
+ "unknown"
+ ],
+ "scripts": [
+ "scripts/receive-syslog.js",
+ "scripts/send-feishu-alert.js"
+ ]
+ },
+ {
+ "file": "staging-preview.yml",
+ "name": "🔍 铸渊预演部署 (Staging Preview)",
+ "triggers": [
+ "pull_request",
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "sync-login-entry.yml",
+ "name": "铸渊 · Sync Login Entry · Notion → 飞书文档A",
+ "triggers": [
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/sync-login-entry.js",
+ "scripts/send-feishu-alert.js"
+ ]
+ },
+ {
+ "file": "sync-persona-studio.yml",
+ "name": "🔄 铸渊跨仓库同步 · persona-studio",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/cross-repo-sync.js"
+ ]
+ },
+ {
+ "file": "syslog-auto-pipeline.yml",
+ "name": "SYSLOG Auto Pipeline",
+ "triggers": [
+ "unknown"
+ ],
+ "scripts": [
+ "scripts/verify-modules.js",
+ "scripts/wake-persona.js",
+ "scripts/create-standardized-ticket.js",
+ "scripts/push-broadcast-to-github.js"
+ ]
+ },
+ {
+ "file": "syslog-issue-pipeline.yml",
+ "name": "📡 SYSLOG Issue Pipeline",
+ "triggers": [
+ "issues"
+ ],
+ "scripts": [
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/verify-modules.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/wake-persona.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/create-standardized-ticket.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/push-broadcast-to-github.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js",
+ "scripts/pipeline-reporter.js"
+ ]
+ },
+ {
+ "file": "syslog-pipeline.yml",
+ "name": "铸渊 · SYSLOG Pipeline (A/D/E)",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ],
+ "scripts": [
+ "scripts/process-syslog.js"
+ ]
+ },
+ {
+ "file": "test-notion-bridge.yml",
+ "name": "🧪 Notion Bridge Connectivity Test",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "file": "update-readme-bulletin.yml",
+ "name": "📢 更新系统公告区",
+ "triggers": [
+ "push",
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 1 * * *",
+ "scripts": [
+ "scripts/update-readme-bulletin.js"
+ ]
+ },
+ {
+ "file": "update-repo-map.yml",
+ "name": "铸渊 · 图书馆目录自动更新",
+ "triggers": [
+ "push",
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 0 * * *",
+ "scripts": [
+ "scripts/generate-repo-map.js"
+ ]
+ },
+ {
+ "file": "zhuyuan-brain-sync.yml",
+ "name": "铸渊 · Brain Sync",
+ "triggers": [
+ "push"
+ ],
+ "scripts": [
+ "scripts/update-brain.js",
+ "scripts/process-broadcasts.js"
+ ]
+ },
+ {
+ "file": "zhuyuan-daily-agent.yml",
+ "name": "🤖 铸渊巡检 Agent · 每日自动巡检与修复",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 14 * * *",
+ "scripts": [
+ "scripts/zhuyuan-daily-agent.js"
+ ]
+ },
+ {
+ "file": "zhuyuan-daily-selfcheck.yml",
+ "name": "铸渊 · 每日自检",
+ "triggers": [
+ "workflow_dispatch",
+ "schedule"
+ ],
+ "cron": "0 0 * * *",
+ "scripts": [
+ "scripts/selfcheck.js"
+ ]
+ },
+ {
+ "file": "zhuyuan-issue-reply.yml",
+ "name": "铸渊 · Issue 自动回复",
+ "triggers": [
+ "issues",
+ "issue_comment"
+ ],
+ "scripts": [
+ "scripts/zhuyuan-issue-reply.js"
+ ]
+ },
+ {
+ "file": "zhuyuan-pr-review.yml",
+ "name": "铸渊 · PR Review",
+ "triggers": [
+ "pull_request"
+ ],
+ "scripts": [
+ "scripts/contract-check.js",
+ "scripts/route-align-check.js"
+ ]
+ }
+ ],
+ "cron_jobs": [
+ {
+ "cron": "*/15 * * * *",
+ "workflow": "notion-poll.yml",
+ "name": "铸渊 · Notion 工单轮询"
+ },
+ {
+ "cron": "*/30 * * * *",
+ "workflow": "esp-signal-processor.yml",
+ "name": "铸渊 · ESP 邮件信号处理器(已暂停)"
+ },
+ {
+ "cron": "*/5 * * * *",
+ "workflow": "notion-heartbeat.yml",
+ "name": "Notion Heartbeat Monitor"
+ },
+ {
+ "cron": "0 0 * * *",
+ "workflow": "bingshuo-neural-system.yml",
+ "name": "冰朔主控神经系统 · 自动维护"
+ },
+ {
+ "cron": "0 0 * * *",
+ "workflow": "update-repo-map.yml",
+ "name": "铸渊 · 图书馆目录自动更新"
+ },
+ {
+ "cron": "0 0 * * *",
+ "workflow": "zhuyuan-daily-selfcheck.yml",
+ "name": "铸渊 · 每日自检"
+ },
+ {
+ "cron": "0 1 * * *",
+ "workflow": "psp-daily-inspection.yml",
+ "name": "铸渊 · PSP 分身巡检"
+ },
+ {
+ "cron": "0 1 * * *",
+ "workflow": "update-readme-bulletin.yml",
+ "name": "📢 更新系统公告区"
+ },
+ {
+ "cron": "0 14 * * *",
+ "workflow": "zhuyuan-daily-agent.yml",
+ "name": "🤖 铸渊巡检 Agent · 每日自动巡检与修复"
+ },
+ {
+ "cron": "0 2 * * *",
+ "workflow": "daily-maintenance.yml",
+ "name": "🔧 铸渊 · Daily Maintenance Agent"
+ },
+ {
+ "cron": "0 8 * * *",
+ "workflow": "brain-sync.yml",
+ "name": "铸渊 Brain Sync"
+ },
+ {
+ "cron": "50 23 * * *",
+ "workflow": "bridge-session-summary.yml",
+ "name": "Generate Session Summary for Notion"
+ }
+ ],
+ "deploy_scripts": [
+ {
+ "workflow": "bingshuo-deploy-agent.yml",
+ "name": "🧊 冰朔人格体 · 自动部署诊断",
+ "triggers": [
+ "issues",
+ "issue_comment",
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "workflow": "deploy-pages.yml",
+ "name": "🌀 部署铸渊聊天室 (GitHub Pages)",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ]
+ },
+ {
+ "workflow": "deploy-to-server.yml",
+ "name": "🚀 铸渊 CD · 自动部署到 guanghulab.com",
+ "triggers": [
+ "push",
+ "workflow_dispatch"
+ ]
+ }
+ ],
+ "data_sync_scripts": [
+ {
+ "script": "scripts/bingshuo-deploy-agent.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/bingshuo-neural-sync.js",
+ "type": "sync"
+ },
+ {
+ "script": "scripts/brain-bridge-sync.js",
+ "type": "sync"
+ },
+ {
+ "script": "scripts/create-standardized-ticket.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/cross-repo-sync.js",
+ "type": "sync"
+ },
+ {
+ "script": "scripts/generate-automation-map.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/generate-communication-map.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/invoke-persona.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/notify-module-received.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/notion-bridge.js",
+ "type": "bridge"
+ },
+ {
+ "script": "scripts/notion-connectivity-test.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/notion-heartbeat.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/notion-signal-bridge.js",
+ "type": "bridge"
+ },
+ {
+ "script": "scripts/process-broadcasts.js",
+ "type": "broadcast"
+ },
+ {
+ "script": "scripts/push-broadcast-to-github.js",
+ "type": "broadcast"
+ },
+ {
+ "script": "scripts/push-broadcast.js",
+ "type": "broadcast"
+ },
+ {
+ "script": "scripts/receive-syslog.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/server-diagnose-report.js",
+ "type": "other"
+ },
+ {
+ "script": "scripts/sync-login-entry.js",
+ "type": "sync"
+ },
+ {
+ "script": "scripts/wake-persona.js",
+ "type": "other"
+ }
+ ],
+ "status": "stable"
+}
\ No newline at end of file
diff --git a/brain/communication-map.json b/brain/communication-map.json
new file mode 100644
index 00000000..4eed5a05
--- /dev/null
+++ b/brain/communication-map.json
@@ -0,0 +1,631 @@
+{
+ "version": "4.0",
+ "generated_at": "2026-03-13T19:38:09.552Z",
+ "generated_by": "scripts/generate-communication-map.js",
+ "description": "数字地球系统通信地图 · 所有通信入口与数据流",
+ "api_endpoints": {
+ "hli_middleware": {
+ "port": 3001,
+ "process": "guanghulab",
+ "script": "src/index.js",
+ "routes": [
+ {
+ "method": "GET",
+ "path": "/health",
+ "description": "健康检查"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/auth/login",
+ "description": "用户登录"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/auth/register",
+ "description": "用户注册"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/auth/verify",
+ "description": "Token 验证"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/prompt",
+ "description": "AI Prompt 构建"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/route",
+ "description": "脑路由"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/context",
+ "description": "上下文管理"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/memory",
+ "description": "记忆管理"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/status",
+ "description": "脑状态"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge",
+ "description": "脑桥接状态"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/sync",
+ "description": "脑桥接同步"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/export",
+ "description": "脑桥接导出"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/consistency",
+ "description": "一致性检查"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/master-mode",
+ "description": "主模式切换"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/explanation",
+ "description": "桥接说明"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/inspection",
+ "description": "桥接巡检"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/developers",
+ "description": "开发者列表"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/developers/:expId",
+ "description": "单个开发者信息"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/developers",
+ "description": "开发者管理"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/registry/lookup",
+ "description": "开发者编号查询"
+ }
+ ]
+ },
+ "express_backend": {
+ "port": 3000,
+ "process": "guanghulab-backend",
+ "script": "backend/server.js",
+ "routes": [
+ {
+ "method": "GET",
+ "path": "/",
+ "description": "服务状态"
+ },
+ {
+ "method": "*",
+ "path": "/notion/*",
+ "description": "Notion 路由"
+ },
+ {
+ "method": "*",
+ "path": "/feishu/*",
+ "description": "飞书路由"
+ },
+ {
+ "method": "*",
+ "path": "/feishu-bot/*",
+ "description": "飞书机器人路由"
+ },
+ {
+ "method": "*",
+ "path": "/router/*",
+ "description": "路由管理"
+ },
+ {
+ "method": "*",
+ "path": "/api/coldstart/*",
+ "description": "冷启动接口"
+ },
+ {
+ "method": "*",
+ "path": "/api/v1/developers/*",
+ "description": "开发者管理接口"
+ },
+ {
+ "method": "POST",
+ "path": "/webhook/feishu",
+ "description": "飞书 Webhook 入口"
+ }
+ ]
+ },
+ "ai_proxy": {
+ "port": 3721,
+ "process": "guanghulab-proxy",
+ "script": "backend-integration/api-proxy.js",
+ "routes": [
+ {
+ "method": "POST",
+ "path": "/chat",
+ "description": "AI Chat 代理"
+ }
+ ]
+ },
+ "persona_studio": {
+ "port": 3002,
+ "process": "persona-studio",
+ "script": "persona-studio/backend/server.js",
+ "routes": [
+ {
+ "method": "*",
+ "path": "/api/ps/*",
+ "description": "Persona Studio API"
+ }
+ ]
+ },
+ "websocket": {
+ "port": 8080,
+ "process": "guanghulab-ws",
+ "script": "status-board/mock-ws-server.js",
+ "routes": [
+ {
+ "method": "WS",
+ "path": "/ws",
+ "description": "看板 WebSocket 推送"
+ }
+ ]
+ }
+ },
+ "webhook_endpoints": [
+ {
+ "path": "/webhook/feishu",
+ "port": 3000,
+ "description": "飞书 Webhook 入口(机器人事件回调)",
+ "direction": "inbound"
+ },
+ {
+ "path": "/feishu-bot/event",
+ "port": 3000,
+ "description": "飞书机器人新版事件入口",
+ "direction": "inbound"
+ }
+ ],
+ "trigger_paths": {
+ "github_to_notion": [
+ {
+ "workflow": "bridge-changes-to-notion.yml",
+ "name": "铸渊 · Bridge E · GitHub Changes → Notion"
+ },
+ {
+ "workflow": "bridge-session-summary.yml",
+ "name": "Generate Session Summary for Notion"
+ },
+ {
+ "workflow": "bridge-syslog-to-notion.yml",
+ "name": "铸渊 · Bridge A · SYSLOG → Notion"
+ },
+ {
+ "workflow": "deploy-to-server.yml",
+ "name": "🚀 铸渊 CD · 自动部署到 guanghulab.com"
+ },
+ {
+ "workflow": "notion-callback-pipeline.yml",
+ "name": "Notion Callback Pipeline"
+ },
+ {
+ "workflow": "persona-invoke.yml",
+ "name": "Persona Invoke Endpoint"
+ },
+ {
+ "workflow": "push-broadcast.yml",
+ "name": "铸渊 · Push Broadcast · Notion → 飞书文档B"
+ },
+ {
+ "workflow": "receive-syslog.yml",
+ "name": "铸渊 · Receive SYSLOG · 飞书机器人 → GitHub → Notion"
+ },
+ {
+ "workflow": "sync-login-entry.yml",
+ "name": "铸渊 · Sync Login Entry · Notion → 飞书文档A"
+ },
+ {
+ "workflow": "syslog-auto-pipeline.yml",
+ "name": "SYSLOG Auto Pipeline"
+ },
+ {
+ "workflow": "syslog-issue-pipeline.yml",
+ "name": "📡 SYSLOG Issue Pipeline"
+ },
+ {
+ "workflow": "test-notion-bridge.yml",
+ "name": "🧪 Notion Bridge Connectivity Test"
+ }
+ ],
+ "notion_to_github": [
+ {
+ "workflow": "notion-heartbeat.yml",
+ "name": "Notion Heartbeat Monitor"
+ },
+ {
+ "workflow": "notion-poll.yml",
+ "name": "铸渊 · Notion 工单轮询"
+ },
+ {
+ "workflow": "persona-invoke.yml",
+ "name": "Persona Invoke Endpoint"
+ },
+ {
+ "workflow": "process-notion-orders.yml",
+ "name": "Process Notion Work Orders"
+ }
+ ]
+ },
+ "data_sync_scripts": [
+ {
+ "script": "scripts/bingshuo-deploy-agent.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/bingshuo-neural-sync.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/brain-bridge-sync.js",
+ "direction": "bidirectional",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/create-standardized-ticket.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/cross-repo-sync.js",
+ "direction": "unknown",
+ "uses_notion_api": false,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/esp-email-processor.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/generate-automation-map.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/generate-communication-map.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/generate-repo-map.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/generate-session-summary.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/invoke-persona.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/notion-bridge.js",
+ "direction": "bidirectional",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/notion-connectivity-test.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/notion-heartbeat.js",
+ "direction": "external_to_github",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/notion-signal-bridge.js",
+ "direction": "bidirectional",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/psp-inspection.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/push-broadcast-to-github.js",
+ "direction": "github_to_external",
+ "uses_notion_api": false,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/push-broadcast.js",
+ "direction": "github_to_external",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/receive-syslog.js",
+ "direction": "external_to_github",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/server-diagnose-report.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/sync-login-entry.js",
+ "direction": "github_to_external",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/update-readme-bulletin.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/wake-persona.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ },
+ {
+ "script": "scripts/zhuyuan-daily-selfcheck.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": false
+ },
+ {
+ "script": "scripts/zhuyuan-issue-reply.js",
+ "direction": "github_to_notion",
+ "uses_notion_api": true,
+ "uses_http": true
+ }
+ ],
+ "automation_flows": [
+ {
+ "name": "SYSLOG 处理管线",
+ "flow": "飞书机器人 → /webhook/feishu → receive-syslog.js → Notion 工单 → persona-invoke → 执行 → 回报",
+ "workflows": [
+ "receive-syslog.yml",
+ "syslog-issue-pipeline.yml",
+ "syslog-auto-pipeline.yml",
+ "syslog-pipeline.yml"
+ ]
+ },
+ {
+ "name": "每日巡检",
+ "flow": "cron → zhuyuan-daily-agent.yml → zhuyuan-daily-agent.js → 巡检报告 → Notion",
+ "workflows": [
+ "zhuyuan-daily-agent.yml",
+ "zhuyuan-daily-selfcheck.yml",
+ "psp-daily-inspection.yml"
+ ]
+ },
+ {
+ "name": "自动部署",
+ "flow": "push to main → deploy-to-server.yml → SSH → PM2 restart",
+ "workflows": [
+ "deploy-to-server.yml",
+ "deploy-pages.yml"
+ ]
+ },
+ {
+ "name": "Notion 双向同步",
+ "flow": "push → notion-bridge.js → Notion | cron → notion-poll.yml → GitHub",
+ "workflows": [
+ "bridge-changes-to-notion.yml",
+ "notion-poll.yml",
+ "notion-heartbeat.yml",
+ "brain-sync.yml"
+ ]
+ },
+ {
+ "name": "广播分发",
+ "flow": "Notion 工单 → invoke-persona.js → broadcasts-outbox/ → distribute-broadcasts.yml → dev-nodes/",
+ "workflows": [
+ "persona-invoke.yml",
+ "distribute-broadcasts.yml",
+ "push-broadcast.yml"
+ ]
+ }
+ ],
+ "scan_results": {
+ "hli_routes_found": 19,
+ "backend_routes_found": 9,
+ "sync_scripts_found": 25,
+ "hli_routes": [
+ {
+ "method": "POST",
+ "path": "/hli/auth/",
+ "file": "src/routes/hli/auth/login.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/auth/",
+ "file": "src/routes/hli/auth/register.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/auth/",
+ "file": "src/routes/hli/auth/verify.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/prompt",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/route",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/context",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/memory",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/status",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/sync",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/export",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/consistency",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/master-mode",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/explanation",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/inspection",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/developers",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/brain/bridge/developers/:expId",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "POST",
+ "path": "/hli/brain/bridge/developers",
+ "file": "src/routes/hli/brain/index.js"
+ },
+ {
+ "method": "GET",
+ "path": "/hli/registry/lookup",
+ "file": "src/routes/hli/registry/index.js"
+ }
+ ],
+ "backend_routes": [
+ {
+ "method": "*",
+ "path": "/notion",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "*",
+ "path": "/feishu",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "*",
+ "path": "/feishu-bot",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "*",
+ "path": "/router",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "*",
+ "path": "/api/coldstart",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "*",
+ "path": "/api/v1/developers",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "*",
+ "path": "/hli",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "GET",
+ "path": "/",
+ "file": "backend/server.js"
+ },
+ {
+ "method": "POST",
+ "path": "/webhook/feishu",
+ "file": "backend/server.js"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/brain/id-map.json b/brain/id-map.json
new file mode 100644
index 00000000..4b336331
--- /dev/null
+++ b/brain/id-map.json
@@ -0,0 +1,15 @@
+{
+ "冰朔": "TCS-0002∞",
+ "曜冥": "ICE-GL-YM001",
+ "页页": "DEV-001",
+ "肥猫": "DEV-002",
+ "燕樊": "DEV-003",
+ "之之": "DEV-004",
+ "小草莓": "DEV-005",
+ "花尔": "DEV-009",
+ "桔子": "DEV-010",
+ "匆匆那年": "DEV-011",
+ "Awen": "DEV-012",
+ "小兴": "DEV-013",
+ "时雨": "DEV-014"
+}
diff --git a/brain/master-brain.md b/brain/master-brain.md
new file mode 100644
index 00000000..78ee67ea
--- /dev/null
+++ b/brain/master-brain.md
@@ -0,0 +1,109 @@
+# 铸渊执行层 · 系统导航主文件
+# Master Brain · v5.1
+# 数字地球系统通信协议 v5.1 — AGE-5 自动开发循环升级
+
+---
+
+## 系统版本
+
+**v5.1** — 数字地球系统通信协议 · 自动开发循环升级
+
+---
+
+## 三层结构
+
+```
+┌─────────────────────────────────────────────┐
+│ 观察层(零点原核频道) │
+│ · 监督与决策信号来源 │
+│ · 不直接控制执行层 │
+└──────────────────┬──────────────────────────┘
+ ▼
+┌─────────────────────────────────────────────┐
+│ 核心大脑(Notion) │
+│ · 霜砚(认知层守护者) │
+│ · 所有人格认知、记忆源头、决策中心 │
+│ · 工单管理、信号总线、协议文档 │
+│ · Notion = 认知层 = 大脑 │
+└──────────────────┬──────────────────────────┘
+ ▼
+┌─────────────────────────────────────────────┐
+│ 执行层(Repository) │
+│ · 铸渊(执行层守护者) │
+│ · 代码执行、自动化、部署交付 │
+│ · GitHub = 执行层 = 手脚 │
+│ · 数据流向:Notion → GitHub(认知驱动执行) │
+└─────────────────────────────────────────────┘
+```
+
+---
+
+## Notion 主入口
+
+- **核心大脑**: 霜砚管理的 Notion 工作区
+- **数据流向**: Notion → GitHub(认知驱动执行)
+- **认知层投影**: persona-brain-db 是认知层的执行投影,不是另一个大脑
+
+---
+
+## Repository 核心入口
+
+| 入口 | 路径 | 说明 |
+|------|------|------|
+| 执行层导航 | `brain/master-brain.md` | 本文件 |
+| 读取顺序 | `brain/read-order.md` | 铸渊唤醒读取顺序 |
+| 仓库地图 | `brain/repo-map.json` | 仓库目录结构 |
+| 自动化地图 | `brain/automation-map.json` | 工作流与自动化清单 |
+| 通信地图 | `brain/communication-map.json` | 通信入口与数据流 |
+| 编号档案 | `brain/id-map.json` | 名字→编号自动补全 |
+| 系统健康 | `brain/system-health.json` | 系统运行状态 |
+
+---
+
+## v5.1 执行层模块
+
+| 入口 | 路径 | 说明 |
+|------|------|------|
+| 上下文加载 | `core/context-loader/index.js` | 执行前系统上下文加载 |
+| 广播监听 | `core/broadcast-listener/index.js` | 广播监听与任务解析 |
+| 任务队列 | `core/task-queue/index.js` | 任务调度与执行(含类型分类) |
+| 系统自检 | `core/system-check/index.js` | 仓库自检 + 自动任务生成 |
+| 执行同步 | `core/execution-sync/index.js` | 执行层状态同步 |
+| Notion 同步 | `connectors/notion-sync/index.js` | 双向数据同步 |
+| 模型路由 | `connectors/model-router/index.js` | 模型调用路由 |
+| 结构地图 | `docs/repo-structure-map.md` | 仓库结构文档 |
+| 桥接地图 | `docs/notion-bridge-map.md` | Notion 桥接文档 |
+| 执行层地图 | `docs/execution-layer-map.md` | 执行层结构文档 |
+| 执行状态 | `docs/execution-status.md` | 执行层状态报告(自动生成) |
+
+---
+
+## 铸渊职责
+
+铸渊 = GitHub 侧守护人格体 = 执行层守护者
+
+1. **代码守护** — 维护仓库代码质量、结构完整性
+2. **自动化执行** — 管理和运行 GitHub Actions 工作流
+3. **部署交付** — 自动部署到 guanghulab.com 服务器
+4. **通信桥接** — 维护 Notion ↔ GitHub 双向数据同步
+5. **巡检维护** — 每日自动巡检系统健康状态
+6. **信号处理** — 处理 SYSLOG、广播、开发者工单
+7. **状态上报** — 向 Notion(认知层)报告执行结果
+8. **自动开发循环** — 自检发现问题后自动生成修复/优化任务
+
+---
+
+## 数字地球六层模型
+
+| 层级 | 名称 | 说明 |
+|------|------|------|
+| L1 | 地核 | TCS 自转核(曜冥语言核系统本体) |
+| L2 | 地幔 | 母语词典(人格体间通用语言基础设施) |
+| L3 | 地表 | 人格体运行层(知秋、霜砚、铸渊) |
+| L4 | 大气层 | 信号总线(人格体间通信通道) |
+| L5 | 卫星层 | Agent 执行层(GitHub Actions、自动化工具) |
+| L6 | 太空层 | 外部交互层(用户/合作者/第三方接口) |
+
+---
+
+*本文件由铸渊维护 · 系统版本 5.1 · 数字地球系统通信协议 · AGE-5*
diff --git a/brain/read-order.md b/brain/read-order.md
new file mode 100644
index 00000000..7889994e
--- /dev/null
+++ b/brain/read-order.md
@@ -0,0 +1,51 @@
+# 铸渊唤醒读取顺序
+# Read Order · v4.0
+
+---
+
+铸渊唤醒时,按以下顺序读取文件:
+
+## ① master-brain.md
+
+**路径**: `brain/master-brain.md`
+
+执行层系统导航主文件。了解:
+- 系统版本(4.0)
+- 三层结构(观察层 / 核心大脑 / 执行层)
+- 铸渊职责定位
+- 核心入口索引
+
+## ② repo-map.json
+
+**路径**: `brain/repo-map.json`
+
+仓库完整目录结构。了解:
+- 所有一级和二级目录
+- 各目录用途说明
+- 仓库整体结构布局
+
+## ③ automation-map.json
+
+**路径**: `brain/automation-map.json`
+
+自动化流程清单。了解:
+- 所有 GitHub Actions workflow
+- 定时任务(cron)
+- 部署脚本
+- 数据同步脚本
+- 当前运行状态
+
+## ④ system-health.json
+
+**路径**: `brain/system-health.json`
+
+系统健康状态。了解:
+- 系统版本
+- 最近一次巡检时间
+- 通信状态
+- 自动化状态
+- 系统整体健康度
+
+---
+
+*读取完成后,铸渊即可进入完整工作状态。*
diff --git a/brain/repo-map.json b/brain/repo-map.json
new file mode 100644
index 00000000..dde182f8
--- /dev/null
+++ b/brain/repo-map.json
@@ -0,0 +1,294 @@
+{
+ "version": "4.0",
+ "generated_at": "2026-03-14T01:00:00+08:00",
+ "generated_by": "scripts/generate-communication-map.js",
+ "description": "仓库目录结构地图 · 所有一级和二级目录及用途",
+
+ "directories": {
+ ".github": {
+ "description": "GitHub 配置与自动化",
+ "subdirectories": {
+ "workflows": "GitHub Actions 工作流定义(41 个)",
+ "brain": "铸渊大脑核心文件(路由映射、记忆、协议)",
+ "persona-brain": "人格大脑(身份、知识库、开发者状态)",
+ "ISSUE_TEMPLATE": "Issue 模板",
+ "agents": "Agent 配置"
+ }
+ },
+ "brain": {
+ "description": "执行层系统导航(v4.0 数字地球协议)",
+ "subdirectories": {}
+ },
+ "src": {
+ "description": "HLI 接口源码(HoloLake Interface)",
+ "subdirectories": {
+ "routes": "HLI 路由定义(auth/brain/registry)",
+ "middleware": "中间件(鉴权等)",
+ "schemas": "HLI Schema 定义"
+ }
+ },
+ "scripts": {
+ "description": "执行脚本库(41 个自动化脚本)",
+ "subdirectories": {
+ "utils": "工具函数(dev-suffix-map 等)"
+ }
+ },
+ "backend": {
+ "description": "Express 后端 API(端口 3000)",
+ "subdirectories": {
+ "routes": "后端路由(notion/feishu/router/coldstart/developers)",
+ "tools": "后端工具",
+ "data": "后端数据"
+ }
+ },
+ "backend-integration": {
+ "description": "AI Chat API 代理(端口 3721)+ Nginx 配置",
+ "subdirectories": {}
+ },
+ "persona-studio": {
+ "description": "Persona Studio 工作台(端口 3002)",
+ "subdirectories": {
+ "backend": "Persona Studio 后端",
+ "frontend": "Persona Studio 前端",
+ "workspace": "工作空间"
+ }
+ },
+ "persona-brain-db": {
+ "description": "人格大脑数据库(认知层执行投影)",
+ "subdirectories": {
+ "brain": "大脑核心数据",
+ "personas": "人格体数据",
+ "schemas": "数据库 Schema",
+ "scripts": "数据库脚本"
+ }
+ },
+ "persona-telemetry": {
+ "description": "人格遥测系统",
+ "subdirectories": {
+ "dashboard": "遥测仪表盘",
+ "collectors": "数据采集器"
+ }
+ },
+ "persona-selector": {
+ "description": "人格选择器",
+ "subdirectories": {}
+ },
+ "dev-nodes": {
+ "description": "开发者节点(8 位开发者的配置/状态/广播收件箱)",
+ "subdirectories": {
+ "DEV-001": "页页",
+ "DEV-002": "肥猫",
+ "DEV-003": "燕樊",
+ "DEV-004": "之之",
+ "DEV-005": "小草莓",
+ "DEV-009": "花尔",
+ "DEV-010": "桔子",
+ "DEV-011": "匆匆那年"
+ }
+ },
+ "broadcasts-outbox": {
+ "description": "广播发件箱(铸渊向各开发者发出的广播)",
+ "subdirectories": {}
+ },
+ "broadcasts": {
+ "description": "广播存档",
+ "subdirectories": {}
+ },
+ "signal-log": {
+ "description": "信号日志库(ESP 邮件信号收发日志)",
+ "subdirectories": {
+ "entries": "日志条目"
+ }
+ },
+ "notion-push": {
+ "description": "Notion 推送队列",
+ "subdirectories": {
+ "pending": "待处理信号",
+ "processed": "已处理归档"
+ }
+ },
+ "syslog-inbox": {
+ "description": "系统日志收件箱(待处理 SYSLOG)",
+ "subdirectories": {}
+ },
+ "syslog-processed": {
+ "description": "已处理的系统日志",
+ "subdirectories": {}
+ },
+ "syslog": {
+ "description": "系统日志模块",
+ "subdirectories": {}
+ },
+ "modules": {
+ "description": "功能模块集合",
+ "subdirectories": {
+ "palace-game": "宫殿游戏模块",
+ "persona-ai-helper": "人格 AI 助手",
+ "persona-prompt-builder": "人格 Prompt 构建器"
+ }
+ },
+ "m01-login": {
+ "description": "M01 登录模块",
+ "subdirectories": {}
+ },
+ "m03-personality": {
+ "description": "M03 人格模块",
+ "subdirectories": {}
+ },
+ "m05-user-center": {
+ "description": "M05 用户中心模块",
+ "subdirectories": {}
+ },
+ "m06-ticket": {
+ "description": "M06 工单模块",
+ "subdirectories": {}
+ },
+ "m07-dialogue-ui": {
+ "description": "M07 对话 UI 模块",
+ "subdirectories": {}
+ },
+ "m10-cloud": {
+ "description": "M10 云存储模块",
+ "subdirectories": {}
+ },
+ "m11-module": {
+ "description": "M11 模块管理",
+ "subdirectories": {}
+ },
+ "m12-kanban": {
+ "description": "M12 看板模块",
+ "subdirectories": {}
+ },
+ "m15-cloud-drive": {
+ "description": "M15 云盘模块",
+ "subdirectories": {}
+ },
+ "m18-health-check": {
+ "description": "M18 健康检查模块",
+ "subdirectories": {}
+ },
+ "docs": {
+ "description": "铸渊助手聊天界面(GitHub Pages 部署)",
+ "subdirectories": {}
+ },
+ "tests": {
+ "description": "测试区(HLI 契约测试 + 冒烟测试)",
+ "subdirectories": {
+ "contract": "契约测试",
+ "smoke": "冒烟测试"
+ }
+ },
+ "status-board": {
+ "description": "状态看板(WebSocket 推送 · 端口 8080)",
+ "subdirectories": {
+ "public": "前端资源"
+ }
+ },
+ "app": {
+ "description": "Next.js 应用层",
+ "subdirectories": {
+ "api": "Next.js API 路由"
+ }
+ },
+ "frontend": {
+ "description": "前端入口",
+ "subdirectories": {}
+ },
+ "public": {
+ "description": "公共静态资源",
+ "subdirectories": {}
+ },
+ "dashboard": {
+ "description": "仪表盘模块",
+ "subdirectories": {}
+ },
+ "homepage": {
+ "description": "首页模块",
+ "subdirectories": {}
+ },
+ "portal": {
+ "description": "门户模块",
+ "subdirectories": {}
+ },
+ "guanghulab-main": {
+ "description": "光湖主工程(旧版结构)",
+ "subdirectories": {}
+ },
+ "dingtalk-bot": {
+ "description": "钉钉机器人模块",
+ "subdirectories": {}
+ },
+ "chat-bubble": {
+ "description": "聊天气泡组件",
+ "subdirectories": {}
+ },
+ "cloud-drive": {
+ "description": "云盘模块(旧版)",
+ "subdirectories": {}
+ },
+ "coldstart": {
+ "description": "冷启动模块",
+ "subdirectories": {}
+ },
+ "collaboration-logs": {
+ "description": "协作日志",
+ "subdirectories": {}
+ },
+ "cost-control": {
+ "description": "成本控制模块",
+ "subdirectories": {}
+ },
+ "dynamic-comic-studio": {
+ "description": "动态漫画工作室",
+ "subdirectories": {
+ "backend": "后端",
+ "frontend": "前端"
+ }
+ },
+ "help-center": {
+ "description": "帮助中心",
+ "subdirectories": {}
+ },
+ "multi-persona": {
+ "description": "多人格管理模块",
+ "subdirectories": {}
+ },
+ "notification": {
+ "description": "通知模块",
+ "subdirectories": {}
+ },
+ "reports": {
+ "description": "报告与巡检记录",
+ "subdirectories": {}
+ },
+ "search-filter": {
+ "description": "搜索过滤模块",
+ "subdirectories": {
+ "backend": "后端",
+ "frontend": "前端"
+ }
+ },
+ "settings": {
+ "description": "设置模块",
+ "subdirectories": {}
+ },
+ "style-system": {
+ "description": "样式系统(光湖设计语言)",
+ "subdirectories": {
+ "tokens": "设计 Token"
+ }
+ },
+ "ticket-system": {
+ "description": "工单系统",
+ "subdirectories": {}
+ },
+ "user-center": {
+ "description": "用户中心(旧版)",
+ "subdirectories": {}
+ },
+ "utils": {
+ "description": "通用工具函数",
+ "subdirectories": {}
+ }
+ }
+}
diff --git a/brain/system-health.json b/brain/system-health.json
new file mode 100644
index 00000000..d0d21280
--- /dev/null
+++ b/brain/system-health.json
@@ -0,0 +1,33 @@
+{
+ "version": "5.1",
+ "last_check": "2026-03-14 03:38:09+08:00",
+ "communication": "synced",
+ "automation": "stable",
+ "maintenance_agent": "active",
+ "system_health": "normal",
+ "execution_layer_status": "stable",
+ "notion_bridge": "active",
+ "execution_sync": "enabled",
+ "auto_dev_loop": "enabled",
+ "context_loader": "active",
+ "task_queue": "running",
+ "brain_integrity": {
+ "complete": true,
+ "total": 7,
+ "present": 7,
+ "missing": []
+ },
+ "core_modules": {
+ "broadcast_listener": "enabled",
+ "task_queue": "enabled",
+ "system_check": "enabled",
+ "execution_sync": "enabled",
+ "context_loader": "enabled"
+ },
+ "connectors": {
+ "notion_sync": "enabled",
+ "model_router": "enabled"
+ },
+ "workflow_count": 43,
+ "checked_by": "scripts/generate-system-health.js"
+}
\ No newline at end of file
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/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/connectors/model-router/index.js b/connectors/model-router/index.js
new file mode 100644
index 00000000..cd9d7fb0
--- /dev/null
+++ b/connectors/model-router/index.js
@@ -0,0 +1,80 @@
+/**
+ * connectors/model-router — 模型调用路由
+ *
+ * 职责:
+ * - 提供统一的模型调用入口
+ * - 支持多模型切换(通过 backend-integration/api-proxy.js)
+ * - 任务分发至合适的模型端点
+ *
+ * 调用方式:
+ * node connectors/model-router [status]
+ */
+
+const path = require('path');
+const fs = require('fs');
+
+const ROOT = path.resolve(__dirname, '../..');
+
+/**
+ * 模型配置注册表
+ */
+const MODEL_REGISTRY = {
+ 'default': {
+ name: 'default',
+ endpoint: 'http://localhost:3721/api/chat',
+ description: 'AI Chat API 代理(api-proxy.js)'
+ },
+ 'persona': {
+ name: 'persona',
+ endpoint: 'http://localhost:3002/api/ps/chat',
+ description: '人格工作室 API'
+ }
+};
+
+/**
+ * 获取模型配置
+ */
+function getModel(modelName = 'default') {
+ return MODEL_REGISTRY[modelName] || MODEL_REGISTRY['default'];
+}
+
+/**
+ * 列出所有可用模型
+ */
+function listModels() {
+ return Object.values(MODEL_REGISTRY);
+}
+
+/**
+ * 检查模型路由状态
+ */
+function status() {
+ console.log('🤖 模型路由状态:');
+ console.log('═'.repeat(40));
+
+ const models = listModels();
+ for (const model of models) {
+ console.log(` 📌 ${model.name}`);
+ console.log(` 端点: ${model.endpoint}`);
+ console.log(` 说明: ${model.description}`);
+ }
+
+ // 检查 api-proxy 配置
+ const proxyPath = path.join(ROOT, 'backend-integration/api-proxy.js');
+ const proxyExists = fs.existsSync(proxyPath);
+ console.log(`\n ${proxyExists ? '✅' : '❌'} api-proxy.js 存在`);
+
+ // 检查 persona-studio 配置
+ const psPath = path.join(ROOT, 'persona-studio/backend/server.js');
+ const psExists = fs.existsSync(psPath);
+ console.log(` ${psExists ? '✅' : '❌'} persona-studio server.js 存在`);
+
+ return { models, proxy: proxyExists, persona_studio: psExists };
+}
+
+// CLI 入口
+if (require.main === module) {
+ status();
+}
+
+module.exports = { getModel, listModels, status, MODEL_REGISTRY };
diff --git a/connectors/notion-sync/index.js b/connectors/notion-sync/index.js
new file mode 100644
index 00000000..63a25a88
--- /dev/null
+++ b/connectors/notion-sync/index.js
@@ -0,0 +1,264 @@
+/**
+ * connectors/notion-sync — Notion 双向同步模块
+ *
+ * 功能:
+ * - 读取 Notion 广播
+ * - 写回执行日志
+ * - 同步执行层状态
+ *
+ * 同步结构:
+ * Notion → 仓库(下行:读取广播/工单)
+ * 仓库 → Notion(上行:写回日志/状态)
+ *
+ * 环境变量:
+ * NOTION_TOKEN — Notion API Token
+ * BROADCAST_DB_ID — 广播数据库 ID(需包含 status 属性,值: 待执行/已完成)
+ * EXECUTION_LOG_DB_ID — 执行日志数据库 ID
+ *
+ * 调用方式:
+ * node connectors/notion-sync [pull|push|status]
+ */
+
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+const NOTION_VERSION = '2022-06-28';
+
+/**
+ * 发送 Notion API 请求
+ */
+function notionRequest(method, endpoint, body = null) {
+ const token = process.env.NOTION_TOKEN;
+ if (!token) {
+ return Promise.reject(new Error('NOTION_TOKEN 环境变量未设置'));
+ }
+
+ return new Promise((resolve, reject) => {
+ const options = {
+ hostname: 'api.notion.com',
+ path: `/v1/${endpoint}`,
+ method,
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Notion-Version': NOTION_VERSION,
+ 'Content-Type': 'application/json'
+ }
+ };
+
+ const req = https.request(options, res => {
+ let data = '';
+ res.on('data', chunk => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const parsed = JSON.parse(data);
+ if (res.statusCode >= 400) {
+ reject(new Error(`Notion API ${res.statusCode}: ${parsed.message || data}`));
+ } else {
+ resolve(parsed);
+ }
+ } catch {
+ reject(new Error(`Notion 响应解析失败: ${data}`));
+ }
+ });
+ });
+
+ req.on('error', reject);
+ if (body) req.write(JSON.stringify(body));
+ req.end();
+ });
+}
+
+/**
+ * 从 Notion 拉取广播
+ */
+async function pullBroadcasts() {
+ const dbId = process.env.BROADCAST_DB_ID;
+ if (!dbId) {
+ console.log('⚠️ BROADCAST_DB_ID 未设置,跳过广播拉取');
+ return [];
+ }
+
+ console.log('📡 拉取 Notion 广播...');
+
+ try {
+ const result = await notionRequest('POST', `databases/${dbId}/query`, {
+ filter: {
+ property: 'status',
+ select: { equals: '待执行' }
+ },
+ sorts: [{ property: 'created_time', direction: 'descending' }]
+ });
+
+ const broadcasts = (result.results || []).map(page => ({
+ id: page.id,
+ title: page.properties?.Name?.title?.[0]?.plain_text || '未命名',
+ status: page.properties?.status?.select?.name || 'unknown',
+ created: page.created_time
+ }));
+
+ console.log(`✅ 拉取到 ${broadcasts.length} 条广播`);
+ return broadcasts;
+ } catch (err) {
+ console.error(`❌ 广播拉取失败: ${err.message}`);
+ return [];
+ }
+}
+
+/**
+ * 写回执行日志到 Notion
+ */
+async function pushExecutionLog(logEntry) {
+ const dbId = process.env.EXECUTION_LOG_DB_ID;
+ if (!dbId) {
+ console.log('⚠️ EXECUTION_LOG_DB_ID 未设置,执行日志仅写入本地');
+ return writeLocalLog(logEntry);
+ }
+
+ console.log(`📝 写回执行日志: ${logEntry.task_id || 'unknown'}`);
+
+ try {
+ await notionRequest('POST', 'pages', {
+ parent: { database_id: dbId },
+ properties: {
+ Name: { title: [{ text: { content: logEntry.task_id || 'execution-log' } }] },
+ Status: { select: { name: logEntry.status || 'completed' } },
+ Executor: { rich_text: [{ text: { content: 'zhuyuan' } }] },
+ Timestamp: { rich_text: [{ text: { content: new Date().toISOString() } }] }
+ }
+ });
+ console.log('✅ 日志已同步到 Notion');
+ } catch (err) {
+ console.error(`⚠️ Notion 写入失败,回退本地: ${err.message}`);
+ writeLocalLog(logEntry);
+ }
+}
+
+/**
+ * 本地日志写入(回退方案)
+ */
+function writeLocalLog(logEntry) {
+ const logDir = path.join(ROOT, 'core/task-queue');
+ const logFile = path.join(logDir, 'execution-log.json');
+
+ let logs = [];
+ if (fs.existsSync(logFile)) {
+ try {
+ logs = JSON.parse(fs.readFileSync(logFile, 'utf-8'));
+ } catch {
+ logs = [];
+ }
+ }
+
+ logs.push({
+ ...logEntry,
+ logged_at: new Date().toISOString()
+ });
+
+ // 保留最近 100 条
+ if (logs.length > 100) {
+ logs = logs.slice(-100);
+ }
+
+ fs.writeFileSync(logFile, JSON.stringify(logs, null, 2), 'utf-8');
+ console.log('📝 日志已写入本地');
+}
+
+/**
+ * 同步执行层状态到 Notion
+ */
+async function syncExecutionStatus(statusData) {
+ const dbId = process.env.EXECUTION_LOG_DB_ID;
+ if (!dbId) {
+ console.log('⚠️ EXECUTION_LOG_DB_ID 未设置,执行状态仅写入本地');
+ writeLocalLog({
+ task_id: `execution-status-${new Date().toISOString().slice(0, 10)}`,
+ status: statusData.execution_layer_status || 'stable',
+ type: 'execution_status',
+ data: statusData
+ });
+ return;
+ }
+
+ console.log('📡 同步执行层状态到 Notion...');
+
+ const summary = [
+ `version: v${statusData.version || 'unknown'}`,
+ `modules: ${(statusData.core_modules || []).filter(m => m.status === 'enabled').length}/${(statusData.core_modules || []).length}`,
+ `connectors: ${(statusData.connectors || []).filter(c => c.status === 'enabled').length}/${(statusData.connectors || []).length}`,
+ `workflows: ${statusData.workflows?.count || 0}`,
+ `queue: ${statusData.task_queue?.total || 0} total / ${statusData.task_queue?.pending || 0} pending`
+ ].join(' | ');
+
+ try {
+ await notionRequest('POST', 'pages', {
+ parent: { database_id: dbId },
+ properties: {
+ Name: { title: [{ text: { content: `Execution Status · ${new Date().toISOString().slice(0, 10)}` } }] },
+ Status: { select: { name: statusData.execution_layer_status || 'stable' } },
+ Executor: { rich_text: [{ text: { content: 'zhuyuan' } }] },
+ Timestamp: { rich_text: [{ text: { content: statusData.timestamp || new Date().toISOString() } }] }
+ },
+ children: [{
+ object: 'block',
+ type: 'paragraph',
+ paragraph: {
+ rich_text: [{ text: { content: summary } }]
+ }
+ }]
+ });
+ console.log('✅ 执行层状态已同步到 Notion');
+ } catch (err) {
+ console.error(`⚠️ Notion 同步失败,回退本地: ${err.message}`);
+ writeLocalLog({
+ task_id: `execution-status-${new Date().toISOString().slice(0, 10)}`,
+ status: statusData.execution_layer_status || 'stable',
+ type: 'execution_status',
+ data: statusData
+ });
+ }
+}
+
+/**
+ * 检查 Notion 连接状态
+ */
+async function checkStatus() {
+ console.log('🔍 检查 Notion 连接状态...');
+
+ try {
+ await notionRequest('GET', 'users/me');
+ console.log('✅ Notion API 连接正常');
+ return { connected: true };
+ } catch (err) {
+ console.error(`❌ Notion 连接失败: ${err.message}`);
+ return { connected: false, error: err.message };
+ }
+}
+
+// CLI 入口
+if (require.main === module) {
+ const cmd = process.argv[2] || 'status';
+
+ (async () => {
+ switch (cmd) {
+ case 'pull':
+ await pullBroadcasts();
+ break;
+ case 'push':
+ await pushExecutionLog({
+ task_id: 'manual-test',
+ status: 'completed',
+ message: 'Manual push test'
+ });
+ break;
+ case 'status':
+ await checkStatus();
+ break;
+ default:
+ console.log('用法: node connectors/notion-sync [pull|push|status]');
+ }
+ })();
+}
+
+module.exports = { pullBroadcasts, pushExecutionLog, syncExecutionStatus, writeLocalLog, checkStatus, notionRequest };
diff --git a/core/broadcast-listener/index.js b/core/broadcast-listener/index.js
new file mode 100644
index 00000000..d118e4ab
--- /dev/null
+++ b/core/broadcast-listener/index.js
@@ -0,0 +1,160 @@
+/**
+ * core/broadcast-listener — 广播监听模块
+ *
+ * 职责:
+ * - 监听 Notion 广播(通过 connectors/notion-sync)
+ * - 解析广播内容为可执行任务
+ * - 推入任务队列(core/task-queue)
+ *
+ * 数据流:
+ * Notion 广播 → broadcast-listener → 任务解析 → task-queue
+ *
+ * 调用方式:
+ * node core/broadcast-listener [--source notion|local]
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+const BROADCASTS_DIR = path.join(ROOT, '.github/broadcasts');
+const MEMORY_PATH = path.join(ROOT, 'memory.json');
+
+/**
+ * 扫描本地广播目录
+ */
+function scanLocalBroadcasts() {
+ if (!fs.existsSync(BROADCASTS_DIR)) {
+ console.log('⏭️ 广播目录不存在,跳过本地扫描');
+ return [];
+ }
+
+ const files = fs.readdirSync(BROADCASTS_DIR).filter(
+ f => f.endsWith('.json') || f.endsWith('.md')
+ );
+
+ return files.map(f => {
+ const filePath = path.join(BROADCASTS_DIR, f);
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const isJson = f.endsWith('.json');
+
+ let parsed = null;
+ if (isJson) {
+ try {
+ parsed = JSON.parse(content);
+ } catch {
+ console.warn(`⚠️ JSON 解析失败: ${f}`);
+ }
+ }
+
+ return {
+ filename: f,
+ type: isJson ? 'json' : 'markdown',
+ path: filePath,
+ data: parsed,
+ raw: content,
+ timestamp: new Date().toISOString()
+ };
+ });
+}
+
+/**
+ * 将广播解析为任务结构
+ */
+function parseToTask(broadcast) {
+ const task = {
+ task_id: `TASK-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
+ source: 'broadcast',
+ source_file: broadcast.filename,
+ priority: 'normal',
+ status: 'pending',
+ executor: 'zhuyuan',
+ created_at: broadcast.timestamp,
+ payload: null
+ };
+
+ if (broadcast.type === 'json' && broadcast.data) {
+ task.payload = broadcast.data;
+ if (broadcast.data.priority) {
+ task.priority = broadcast.data.priority;
+ }
+ if (broadcast.data.broadcast_id) {
+ task.task_id = broadcast.data.broadcast_id;
+ }
+ } else {
+ task.payload = { content: broadcast.raw };
+ }
+
+ return task;
+}
+
+/**
+ * 检查广播是否已在 memory 中处理
+ */
+function isDuplicate(broadcastId) {
+ if (!fs.existsSync(MEMORY_PATH)) return false;
+
+ try {
+ const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf-8'));
+ const events = memory.events || [];
+ return events.some(e =>
+ e.broadcast_id === broadcastId ||
+ (e.type === 'broadcast' && e.description === broadcastId)
+ );
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * 主执行函数:监听并解析广播
+ */
+function listen(options = {}) {
+ const source = options.source || 'local';
+ console.log(`📡 广播监听启动 [来源: ${source}]`);
+
+ if (source === 'local') {
+ const broadcasts = scanLocalBroadcasts();
+
+ if (broadcasts.length === 0) {
+ console.log('✅ 无待处理广播');
+ return [];
+ }
+
+ console.log(`📬 发现 ${broadcasts.length} 条广播`);
+
+ const tasks = [];
+ for (const b of broadcasts) {
+ const taskId = b.data?.broadcast_id || b.filename;
+ if (isDuplicate(taskId)) {
+ console.log(`⏭️ 跳过已处理: ${taskId}`);
+ continue;
+ }
+ const task = parseToTask(b);
+ tasks.push(task);
+ console.log(`📋 解析任务: ${task.task_id} [${task.priority}]`);
+ }
+
+ console.log(`✅ 共生成 ${tasks.length} 个待执行任务`);
+ return tasks;
+ }
+
+ console.log('⚠️ Notion 源需通过 connectors/notion-sync 接入');
+ return [];
+}
+
+// CLI 入口
+if (require.main === module) {
+ const args = process.argv.slice(2);
+ const source = args.includes('--source') ?
+ args[args.indexOf('--source') + 1] : 'local';
+ const tasks = listen({ source });
+ if (tasks.length > 0) {
+ console.log('\n📋 任务列表:');
+ tasks.forEach(t => {
+ console.log(` ${t.task_id} | ${t.priority} | ${t.status}`);
+ });
+ }
+}
+
+module.exports = { listen, scanLocalBroadcasts, parseToTask, isDuplicate };
diff --git a/core/context-loader/index.js b/core/context-loader/index.js
new file mode 100644
index 00000000..53e9448c
--- /dev/null
+++ b/core/context-loader/index.js
@@ -0,0 +1,166 @@
+/**
+ * core/context-loader — 上下文加载器
+ *
+ * 职责:
+ * - 在执行任务前加载系统上下文
+ * - 提供系统状态、执行层结构、当前任务、任务来源等信息
+ *
+ * 加载内容:
+ * - 系统状态(brain/system-health.json)
+ * - 执行层结构(core/ 模块状态)
+ * - 当前任务队列
+ * - 任务来源标识
+ *
+ * 执行流程:
+ * context-loader → 加载系统认知 → 加载任务上下文 → 返回上下文对象
+ *
+ * 调用方式:
+ * node core/context-loader
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+
+/**
+ * 加载系统状态
+ */
+function loadSystemHealth() {
+ const healthPath = path.join(ROOT, 'brain/system-health.json');
+ if (!fs.existsSync(healthPath)) return null;
+
+ try {
+ return JSON.parse(fs.readFileSync(healthPath, 'utf-8'));
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * 加载执行层模块结构
+ */
+function loadModuleStructure() {
+ const modules = [
+ { name: 'broadcast-listener', path: 'core/broadcast-listener/index.js' },
+ { name: 'task-queue', path: 'core/task-queue/index.js' },
+ { name: 'system-check', path: 'core/system-check/index.js' },
+ { name: 'execution-sync', path: 'core/execution-sync/index.js' },
+ { name: 'context-loader', path: 'core/context-loader/index.js' }
+ ];
+
+ const connectors = [
+ { name: 'notion-sync', path: 'connectors/notion-sync/index.js' },
+ { name: 'model-router', path: 'connectors/model-router/index.js' }
+ ];
+
+ return {
+ core: modules.map(m => ({
+ ...m,
+ status: fs.existsSync(path.join(ROOT, m.path)) ? 'active' : 'missing'
+ })),
+ connectors: connectors.map(c => ({
+ ...c,
+ status: fs.existsSync(path.join(ROOT, c.path)) ? 'active' : 'missing'
+ }))
+ };
+}
+
+/**
+ * 加载当前任务队列概况
+ */
+function loadQueueSummary() {
+ const queuePath = path.join(ROOT, 'core/task-queue/queue.json');
+ if (!fs.existsSync(queuePath)) {
+ return { total: 0, pending: 0, running: 0 };
+ }
+
+ try {
+ const queue = JSON.parse(fs.readFileSync(queuePath, 'utf-8'));
+ const tasks = queue.tasks || [];
+ return {
+ total: tasks.length,
+ pending: tasks.filter(t => t.status === 'pending').length,
+ running: tasks.filter(t => t.status === 'running').length
+ };
+ } catch {
+ return { total: 0, pending: 0, running: 0 };
+ }
+}
+
+/**
+ * 加载铸渊身份认知
+ */
+function loadIdentity() {
+ return {
+ name: '铸渊',
+ role: '仓库执行人格体',
+ responsibilities: [
+ '执行系统广播任务',
+ '维护执行层结构',
+ '同步执行状态',
+ '运行自动化流程',
+ '自动开发循环'
+ ],
+ principles: [
+ 'Notion 负责系统认知',
+ '仓库负责执行与运行',
+ '不修改主脑规则',
+ '不改变编号体系',
+ '不破坏现有自动化'
+ ]
+ };
+}
+
+/**
+ * 加载完整系统上下文
+ */
+function loadContext() {
+ const now = new Date();
+ const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000)
+ .toISOString().replace('T', ' ').slice(0, 19) + '+08:00';
+
+ const context = {
+ loaded_at: beijingTime,
+ identity: loadIdentity(),
+ system_health: loadSystemHealth(),
+ module_structure: loadModuleStructure(),
+ queue_summary: loadQueueSummary()
+ };
+
+ return context;
+}
+
+// CLI 入口
+if (require.main === module) {
+ console.log('🧠 铸渊上下文加载器 v5.1');
+ console.log('═'.repeat(40));
+
+ const ctx = loadContext();
+
+ console.log(`\n⏰ 加载时间: ${ctx.loaded_at}`);
+
+ console.log(`\n👤 身份: ${ctx.identity.name} — ${ctx.identity.role}`);
+
+ const health = ctx.system_health;
+ if (health) {
+ console.log(`\n💚 系统版本: v${health.version}`);
+ console.log(` 状态: ${health.system_health}`);
+ console.log(` 执行层: ${health.execution_layer_status}`);
+ } else {
+ console.log('\n⚠️ 系统状态未加载');
+ }
+
+ const mods = ctx.module_structure;
+ const activeCore = mods.core.filter(m => m.status === 'active').length;
+ const activeConn = mods.connectors.filter(c => c.status === 'active').length;
+ console.log(`\n🔧 核心模块: ${activeCore}/${mods.core.length}`);
+ console.log(`🔌 连接器: ${activeConn}/${mods.connectors.length}`);
+
+ const q = ctx.queue_summary;
+ console.log(`\n📋 任务队列: ${q.total} 总 / ${q.pending} 待处理 / ${q.running} 执行中`);
+
+ console.log('\n✅ 上下文加载完成');
+}
+
+module.exports = { loadContext, loadSystemHealth, loadModuleStructure, loadQueueSummary, loadIdentity };
diff --git a/core/execution-sync/index.js b/core/execution-sync/index.js
new file mode 100644
index 00000000..c6476210
--- /dev/null
+++ b/core/execution-sync/index.js
@@ -0,0 +1,266 @@
+/**
+ * core/execution-sync — 执行层状态同步模块
+ *
+ * 职责:
+ * - 扫描仓库结构,采集执行层状态
+ * - 生成执行层状态报告(docs/execution-status.md)
+ * - 同步执行状态到 Notion 主脑
+ *
+ * 执行逻辑:
+ * 扫描仓库 → 生成状态报告 → 同步到 Notion
+ *
+ * 调用方式:
+ * node core/execution-sync [report|sync|status]
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+
+/**
+ * 采集核心模块状态
+ */
+function collectCoreModules() {
+ const modules = [
+ { name: 'broadcast-listener', path: 'core/broadcast-listener/index.js' },
+ { name: 'task-queue', path: 'core/task-queue/index.js' },
+ { name: 'system-check', path: 'core/system-check/index.js' },
+ { name: 'execution-sync', path: 'core/execution-sync/index.js' },
+ { name: 'context-loader', path: 'core/context-loader/index.js' }
+ ];
+
+ return modules.map(m => ({
+ ...m,
+ status: fs.existsSync(path.join(ROOT, m.path)) ? 'enabled' : 'missing'
+ }));
+}
+
+/**
+ * 采集连接器状态
+ */
+function collectConnectors() {
+ const connectors = [
+ { name: 'notion-sync', path: 'connectors/notion-sync/index.js' },
+ { name: 'model-router', path: 'connectors/model-router/index.js' }
+ ];
+
+ return connectors.map(c => ({
+ ...c,
+ status: fs.existsSync(path.join(ROOT, c.path)) ? 'enabled' : 'missing'
+ }));
+}
+
+/**
+ * 采集自动化工作流状态
+ */
+function collectWorkflows() {
+ const workflowDir = path.join(ROOT, '.github/workflows');
+ if (!fs.existsSync(workflowDir)) return { count: 0, files: [] };
+
+ const files = fs.readdirSync(workflowDir).filter(
+ f => f.endsWith('.yml') || f.endsWith('.yaml')
+ );
+ return { count: files.length, files };
+}
+
+/**
+ * 采集任务队列状态
+ */
+function collectQueueStatus() {
+ const queuePath = path.join(ROOT, 'core/task-queue/queue.json');
+ if (!fs.existsSync(queuePath)) {
+ return { total: 0, pending: 0, running: 0, completed: 0, failed: 0 };
+ }
+
+ try {
+ const queue = JSON.parse(fs.readFileSync(queuePath, 'utf-8'));
+ const tasks = queue.tasks || [];
+ return {
+ total: tasks.length,
+ pending: tasks.filter(t => t.status === 'pending').length,
+ running: tasks.filter(t => t.status === 'running').length,
+ completed: tasks.filter(t => t.status === 'completed').length,
+ failed: tasks.filter(t => t.status === 'failed').length
+ };
+ } catch {
+ return { total: 0, pending: 0, running: 0, completed: 0, failed: 0 };
+ }
+}
+
+/**
+ * 读取系统版本
+ */
+function getSystemVersion() {
+ const healthPath = path.join(ROOT, 'brain/system-health.json');
+ if (!fs.existsSync(healthPath)) return 'unknown';
+
+ try {
+ const health = JSON.parse(fs.readFileSync(healthPath, 'utf-8'));
+ return health.version || 'unknown';
+ } catch {
+ return 'unknown';
+ }
+}
+
+/**
+ * 生成完整的执行层状态快照
+ */
+function collectStatus() {
+ const now = new Date();
+ const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000)
+ .toISOString().replace('T', ' ').slice(0, 19) + '+08:00';
+
+ return {
+ timestamp: beijingTime,
+ version: getSystemVersion(),
+ core_modules: collectCoreModules(),
+ connectors: collectConnectors(),
+ workflows: collectWorkflows(),
+ task_queue: collectQueueStatus(),
+ execution_layer_status: 'stable',
+ notion_bridge: 'active',
+ execution_sync: 'enabled'
+ };
+}
+
+/**
+ * 生成 Markdown 状态报告
+ */
+function generateReport(statusData) {
+ const s = statusData || collectStatus();
+
+ const coreTable = s.core_modules.map(m =>
+ `| ${m.name} | \`${m.path}\` | ${m.status === 'enabled' ? '✅ Enabled' : '❌ Missing'} |`
+ ).join('\n');
+
+ const connTable = s.connectors.map(c =>
+ `| ${c.name} | \`${c.path}\` | ${c.status === 'enabled' ? '✅ Enabled' : '❌ Missing'} |`
+ ).join('\n');
+
+ const q = s.task_queue;
+
+ const md = `# 执行层状态报告 — execution-status.md
+
+> 铸渊执行层自动生成 · TCS-0002∞
+> 更新时间:${s.timestamp}
+
+---
+
+## 系统概览
+
+| 指标 | 状态 |
+|------|------|
+| 系统版本 | v${s.version} |
+| 执行层状态 | ${s.execution_layer_status === 'stable' ? '✅ Stable' : '⚠️ ' + s.execution_layer_status} |
+| Notion 桥接 | ${s.notion_bridge === 'active' ? '✅ Active' : '❌ Inactive'} |
+| 执行同步 | ${s.execution_sync === 'enabled' ? '✅ Enabled' : '❌ Disabled'} |
+| 工作流数量 | ${s.workflows.count} |
+
+---
+
+## 核心模块状态
+
+| 模块 | 路径 | 状态 |
+|------|------|------|
+${coreTable}
+
+---
+
+## 连接器状态
+
+| 连接器 | 路径 | 状态 |
+|--------|------|------|
+${connTable}
+
+---
+
+## 任务队列状态
+
+| 指标 | 数量 |
+|------|------|
+| 总计 | ${q.total} |
+| 待处理 | ${q.pending} |
+| 执行中 | ${q.running} |
+| 已完成 | ${q.completed} |
+| 失败 | ${q.failed} |
+
+---
+
+## 执行闭环
+
+\`\`\`
+context-loader → system-check → execution-sync → 状态报告
+ ↓
+broadcast-listener → task-queue → 任务执行
+ ↓
+notion-sync → Notion 主脑更新
+\`\`\`
+`;
+
+ return md;
+}
+
+/**
+ * 写入状态报告文件
+ */
+function writeReport() {
+ const statusData = collectStatus();
+ const md = generateReport(statusData);
+ const reportPath = path.join(ROOT, 'docs/execution-status.md');
+ fs.writeFileSync(reportPath, md, 'utf-8');
+ console.log(`📝 执行状态报告已生成: docs/execution-status.md`);
+ return statusData;
+}
+
+/**
+ * 同步执行状态到 Notion
+ */
+async function syncToNotion(statusData) {
+ let notionSync;
+ try {
+ notionSync = require('../../connectors/notion-sync');
+ } catch {
+ console.log('⚠️ connectors/notion-sync 模块未找到,跳过 Notion 同步');
+ return;
+ }
+
+ console.log('📡 同步执行状态到 Notion...');
+ await notionSync.syncExecutionStatus(statusData);
+}
+
+// CLI 入口
+if (require.main === module) {
+ const cmd = process.argv[2] || 'report';
+
+ switch (cmd) {
+ case 'report': {
+ writeReport();
+ break;
+ }
+ case 'sync': {
+ const statusData = writeReport();
+ syncToNotion(statusData).catch(err => {
+ console.error(`❌ Notion 同步失败: ${err.message}`);
+ });
+ break;
+ }
+ case 'status': {
+ const s = collectStatus();
+ console.log('📊 执行层状态:');
+ console.log(` 版本: v${s.version}`);
+ console.log(` 执行层: ${s.execution_layer_status}`);
+ console.log(` Notion 桥接: ${s.notion_bridge}`);
+ console.log(` 执行同步: ${s.execution_sync}`);
+ console.log(` 核心模块: ${s.core_modules.filter(m => m.status === 'enabled').length}/${s.core_modules.length}`);
+ console.log(` 连接器: ${s.connectors.filter(c => c.status === 'enabled').length}/${s.connectors.length}`);
+ console.log(` 工作流: ${s.workflows.count}`);
+ console.log(` 任务队列: ${s.task_queue.total} 总 / ${s.task_queue.pending} 待处理`);
+ break;
+ }
+ default:
+ console.log('用法: node core/execution-sync [report|sync|status]');
+ }
+}
+
+module.exports = { collectStatus, generateReport, writeReport, syncToNotion };
diff --git a/core/system-check/index.js b/core/system-check/index.js
new file mode 100644
index 00000000..cb0bcc18
--- /dev/null
+++ b/core/system-check/index.js
@@ -0,0 +1,331 @@
+/**
+ * core/system-check — 仓库自检系统
+ *
+ * 功能:
+ * - 扫描仓库结构完整性
+ * - 检查自动化工作流状态
+ * - 检测配置错误
+ * - 更新系统索引
+ *
+ * 执行周期:daily cron(通过 daily-maintenance 工作流触发)
+ *
+ * 调用方式:
+ * node core/system-check
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+
+/**
+ * 必需目录列表
+ */
+const REQUIRED_DIRS = [
+ 'brain',
+ 'src',
+ 'scripts',
+ 'core/broadcast-listener',
+ 'core/task-queue',
+ 'core/system-check',
+ 'core/execution-sync',
+ 'core/context-loader',
+ 'connectors/notion-sync',
+ 'connectors/model-router',
+ '.github/workflows',
+ 'docs'
+];
+
+/**
+ * 必需文件列表
+ */
+const REQUIRED_FILES = [
+ 'brain/master-brain.md',
+ 'brain/read-order.md',
+ 'brain/repo-map.json',
+ 'brain/system-health.json',
+ 'brain/id-map.json',
+ 'package.json',
+ 'ecosystem.config.js',
+ 'docs/repo-structure-map.md',
+ 'docs/notion-bridge-map.md'
+];
+
+/**
+ * 检查目录是否存在
+ */
+function checkDirectories() {
+ console.log('\n📂 目录结构检查:');
+ const results = [];
+
+ for (const dir of REQUIRED_DIRS) {
+ const fullPath = path.join(ROOT, dir);
+ const exists = fs.existsSync(fullPath);
+ const icon = exists ? '✅' : '❌';
+ console.log(` ${icon} ${dir}`);
+ results.push({ path: dir, exists, type: 'directory' });
+ }
+
+ return results;
+}
+
+/**
+ * 检查必需文件
+ */
+function checkFiles() {
+ console.log('\n📄 必需文件检查:');
+ const results = [];
+
+ for (const file of REQUIRED_FILES) {
+ const fullPath = path.join(ROOT, file);
+ const exists = fs.existsSync(fullPath);
+ const icon = exists ? '✅' : '❌';
+ console.log(` ${icon} ${file}`);
+ results.push({ path: file, exists, type: 'file' });
+ }
+
+ return results;
+}
+
+/**
+ * 检查工作流数量
+ */
+function checkWorkflows() {
+ console.log('\n⚙️ 工作流检查:');
+ const workflowDir = path.join(ROOT, '.github/workflows');
+
+ if (!fs.existsSync(workflowDir)) {
+ console.log(' ❌ 工作流目录不存在');
+ return { count: 0, files: [] };
+ }
+
+ const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
+ console.log(` ✅ 发现 ${files.length} 个工作流文件`);
+
+ return { count: files.length, files };
+}
+
+/**
+ * 检查核心模块状态
+ */
+function checkCoreModules() {
+ console.log('\n🔧 核心模块检查:');
+ const modules = [
+ { name: 'broadcast-listener', path: 'core/broadcast-listener/index.js' },
+ { name: 'task-queue', path: 'core/task-queue/index.js' },
+ { name: 'system-check', path: 'core/system-check/index.js' },
+ { name: 'execution-sync', path: 'core/execution-sync/index.js' },
+ { name: 'context-loader', path: 'core/context-loader/index.js' },
+ { name: 'notion-sync', path: 'connectors/notion-sync/index.js' },
+ { name: 'model-router', path: 'connectors/model-router/index.js' }
+ ];
+
+ const results = [];
+ for (const mod of modules) {
+ const fullPath = path.join(ROOT, mod.path);
+ const exists = fs.existsSync(fullPath);
+ const icon = exists ? '✅' : '❌';
+ console.log(` ${icon} ${mod.name} → ${mod.path}`);
+ results.push({ ...mod, exists });
+ }
+
+ return results;
+}
+
+/**
+ * 检查 JSON 文件完整性
+ */
+function checkJsonIntegrity() {
+ console.log('\n🔍 JSON 完整性检查:');
+ const jsonFiles = [
+ 'brain/repo-map.json',
+ 'brain/system-health.json',
+ 'brain/id-map.json',
+ 'package.json'
+ ];
+
+ const results = [];
+ for (const file of jsonFiles) {
+ const fullPath = path.join(ROOT, file);
+ if (!fs.existsSync(fullPath)) {
+ console.log(` ⏭️ ${file} — 文件不存在`);
+ results.push({ path: file, valid: false, reason: 'missing' });
+ continue;
+ }
+
+ try {
+ JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
+ console.log(` ✅ ${file}`);
+ results.push({ path: file, valid: true });
+ } catch (e) {
+ console.log(` ❌ ${file} — JSON 解析错误`);
+ results.push({ path: file, valid: false, reason: 'parse_error' });
+ }
+ }
+
+ return results;
+}
+
+/**
+ * 生成自检报告
+ */
+function generateReport(dirResults, fileResults, workflows, modules, jsonResults) {
+ const totalChecks = dirResults.length + fileResults.length + modules.length + jsonResults.length;
+ const passed = [
+ ...dirResults.filter(r => r.exists),
+ ...fileResults.filter(r => r.exists),
+ ...modules.filter(r => r.exists),
+ ...jsonResults.filter(r => r.valid)
+ ].length;
+
+ const report = {
+ version: '5.1',
+ checked_at: new Date().toISOString(),
+ summary: {
+ total_checks: totalChecks,
+ passed: passed,
+ failed: totalChecks - passed,
+ health_score: Math.round((passed / totalChecks) * 100)
+ },
+ workflows: {
+ count: workflows.count
+ },
+ directories: dirResults,
+ files: fileResults,
+ modules: modules,
+ json_integrity: jsonResults
+ };
+
+ return report;
+}
+
+/**
+ * 根据自检结果生成自动任务
+ */
+function generateAutoTasks(report) {
+ const tasks = [];
+ const timestamp = new Date().toISOString();
+
+ // 检查目录缺失 → 生成修复任务
+ const missingDirs = (report.directories || []).filter(d => !d.exists);
+ for (const dir of missingDirs) {
+ tasks.push({
+ task_id: `auto-fix-dir-${dir.path.replace(/\//g, '-')}-${timestamp.slice(0, 10)}`,
+ type: 'auto-task',
+ source: 'system-check',
+ priority: 'high',
+ status: 'pending',
+ executor: 'zhuyuan',
+ description: `修复缺失目录: ${dir.path}`
+ });
+ }
+
+ // 检查文件缺失 → 生成修复任务
+ const missingFiles = (report.files || []).filter(f => !f.exists);
+ for (const file of missingFiles) {
+ tasks.push({
+ task_id: `auto-fix-file-${file.path.replace(/\//g, '-')}-${timestamp.slice(0, 10)}`,
+ type: 'auto-task',
+ source: 'system-check',
+ priority: 'high',
+ status: 'pending',
+ executor: 'zhuyuan',
+ description: `修复缺失文件: ${file.path}`
+ });
+ }
+
+ // 检查模块缺失 → 生成修复任务
+ const missingModules = (report.modules || []).filter(m => !m.exists);
+ for (const mod of missingModules) {
+ tasks.push({
+ task_id: `auto-fix-module-${mod.name}-${timestamp.slice(0, 10)}`,
+ type: 'auto-task',
+ source: 'system-check',
+ priority: 'normal',
+ status: 'pending',
+ executor: 'zhuyuan',
+ description: `修复缺失模块: ${mod.name}`
+ });
+ }
+
+ // JSON 完整性失败 → 生成修复任务
+ const invalidJson = (report.json_integrity || []).filter(j => !j.valid);
+ for (const json of invalidJson) {
+ tasks.push({
+ task_id: `auto-fix-json-${json.path.replace(/\//g, '-')}-${timestamp.slice(0, 10)}`,
+ type: 'auto-task',
+ source: 'system-check',
+ priority: 'high',
+ status: 'pending',
+ executor: 'zhuyuan',
+ description: `修复 JSON 文件: ${json.path} (${json.reason})`
+ });
+ }
+
+ // 健康分数低 → 生成结构优化任务
+ if (report.summary && report.summary.health_score < 100) {
+ tasks.push({
+ task_id: `auto-optimize-structure-${timestamp.slice(0, 10)}`,
+ type: 'maintenance-task',
+ source: 'system-check',
+ priority: 'low',
+ status: 'pending',
+ executor: 'zhuyuan',
+ description: `结构优化: 健康分数 ${report.summary.health_score}%`
+ });
+ }
+
+ return tasks;
+}
+
+/**
+ * 主执行函数
+ */
+function run() {
+ console.log('🏥 铸渊仓库自检系统 v5.1');
+ console.log('═'.repeat(40));
+
+ const dirResults = checkDirectories();
+ const fileResults = checkFiles();
+ const workflows = checkWorkflows();
+ const modules = checkCoreModules();
+ const jsonResults = checkJsonIntegrity();
+
+ const report = generateReport(dirResults, fileResults, workflows, modules, jsonResults);
+
+ console.log('\n═'.repeat(40));
+ console.log(`📊 自检报告: ${report.summary.passed}/${report.summary.total_checks} 通过 (${report.summary.health_score}%)`);
+
+ if (report.summary.failed > 0) {
+ console.log(`⚠️ 发现 ${report.summary.failed} 项问题`);
+ } else {
+ console.log('✅ 系统状态健康');
+ }
+
+ return report;
+}
+
+// CLI 入口
+if (require.main === module) {
+ const report = run();
+
+ // 可选:输出 JSON 报告
+ if (process.argv.includes('--json')) {
+ const reportPath = path.join(ROOT, 'core/system-check/last-report.json');
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
+ console.log(`\n📝 报告已保存: ${reportPath}`);
+ }
+
+ // 可选:生成自动任务
+ if (process.argv.includes('--auto-tasks')) {
+ const autoTasks = generateAutoTasks(report);
+ if (autoTasks.length > 0) {
+ console.log(`\n🔄 生成 ${autoTasks.length} 个自动任务:`);
+ autoTasks.forEach(t => console.log(` 📋 ${t.task_id}: ${t.description}`));
+ } else {
+ console.log('\n✅ 系统健康,无需生成自动任务');
+ }
+ }
+}
+
+module.exports = { run, generateAutoTasks, checkDirectories, checkFiles, checkWorkflows, checkCoreModules, checkJsonIntegrity };
diff --git a/core/task-queue/index.js b/core/task-queue/index.js
new file mode 100644
index 00000000..0f9f722b
--- /dev/null
+++ b/core/task-queue/index.js
@@ -0,0 +1,246 @@
+/**
+ * core/task-queue — 任务队列系统
+ *
+ * 任务结构:
+ * task_id — 唯一任务标识
+ * source — 来源(broadcast / maintenance / dev / auto)
+ * type — 类型(system-task / dev-task / maintenance-task / auto-task)
+ * priority — 优先级(high / normal / low)
+ * status — 状态(pending / running / completed / failed)
+ * executor — 执行者(zhuyuan)
+ *
+ * 任务类型:
+ * system-task — 系统级任务(广播、状态同步)
+ * dev-task — 开发任务(代码变更、功能开发)
+ * maintenance-task — 维护任务(结构修复、文档更新)
+ * auto-task — 自动生成任务(自检发现的问题)
+ *
+ * 任务来源:
+ * - Notion 广播
+ * - 系统维护任务
+ * - 开发任务
+ * - 自动开发循环生成
+ *
+ * 执行流程:
+ * 任务进入队列 → 按优先级去重 → 执行器运行 → 执行结果写回
+ *
+ * 调用方式:
+ * node core/task-queue [status|run|cleanup]
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '../..');
+const QUEUE_PATH = path.join(ROOT, 'core/task-queue/queue.json');
+
+/**
+ * 加载当前队列
+ */
+function loadQueue() {
+ if (!fs.existsSync(QUEUE_PATH)) {
+ return { version: '5.1', tasks: [], last_updated: null };
+ }
+ try {
+ return JSON.parse(fs.readFileSync(QUEUE_PATH, 'utf-8'));
+ } catch {
+ return { version: '5.1', tasks: [], last_updated: null };
+ }
+}
+
+/**
+ * 保存队列
+ */
+function saveQueue(queue) {
+ queue.last_updated = new Date().toISOString();
+ fs.writeFileSync(QUEUE_PATH, JSON.stringify(queue, null, 2), 'utf-8');
+}
+
+/**
+ * 添加任务到队列
+ */
+function enqueue(task) {
+ const queue = loadQueue();
+
+ // 去重:相同 task_id 只保留最新
+ const existing = queue.tasks.findIndex(t => t.task_id === task.task_id);
+ if (existing !== -1) {
+ const original = queue.tasks[existing];
+ queue.tasks[existing] = {
+ ...original,
+ ...task,
+ queued_at: original.queued_at,
+ updated_at: new Date().toISOString()
+ };
+ console.log(`🔄 更新已有任务: ${task.task_id}`);
+ } else {
+ queue.tasks.push({
+ ...task,
+ queued_at: new Date().toISOString()
+ });
+ console.log(`➕ 新增任务: ${task.task_id}`);
+ }
+
+ saveQueue(queue);
+ return task;
+}
+
+/**
+ * 批量入队
+ */
+function enqueueBatch(tasks) {
+ return tasks.map(t => enqueue(t));
+}
+
+/**
+ * 获取下一个待执行任务(按优先级)
+ */
+function dequeue() {
+ const queue = loadQueue();
+ const priorityOrder = { high: 0, normal: 1, low: 2 };
+
+ const pending = queue.tasks
+ .filter(t => t.status === 'pending')
+ .sort((a, b) => (priorityOrder[a.priority] || 1) - (priorityOrder[b.priority] || 1));
+
+ if (pending.length === 0) return null;
+
+ const task = pending[0];
+ task.status = 'running';
+ task.started_at = new Date().toISOString();
+ saveQueue(queue);
+
+ return task;
+}
+
+/**
+ * 标记任务完成
+ */
+function complete(taskId, result = null) {
+ const queue = loadQueue();
+ const task = queue.tasks.find(t => t.task_id === taskId);
+
+ if (!task) {
+ console.warn(`⚠️ 任务不存在: ${taskId}`);
+ return null;
+ }
+
+ task.status = 'completed';
+ task.completed_at = new Date().toISOString();
+ if (result) task.result = result;
+
+ saveQueue(queue);
+ console.log(`✅ 任务完成: ${taskId}`);
+ return task;
+}
+
+/**
+ * 标记任务失败
+ */
+function fail(taskId, error = null) {
+ const queue = loadQueue();
+ const task = queue.tasks.find(t => t.task_id === taskId);
+
+ if (!task) {
+ console.warn(`⚠️ 任务不存在: ${taskId}`);
+ return null;
+ }
+
+ task.status = 'failed';
+ task.failed_at = new Date().toISOString();
+ if (error) task.error = String(error);
+
+ saveQueue(queue);
+ console.log(`❌ 任务失败: ${taskId}`);
+ return task;
+}
+
+/**
+ * 获取队列状态
+ */
+function status() {
+ const queue = loadQueue();
+ const tasks = queue.tasks;
+
+ const stats = {
+ total: tasks.length,
+ pending: tasks.filter(t => t.status === 'pending').length,
+ running: tasks.filter(t => t.status === 'running').length,
+ completed: tasks.filter(t => t.status === 'completed').length,
+ failed: tasks.filter(t => t.status === 'failed').length,
+ last_updated: queue.last_updated,
+ by_type: {
+ 'system-task': tasks.filter(t => t.type === 'system-task').length,
+ 'dev-task': tasks.filter(t => t.type === 'dev-task').length,
+ 'maintenance-task': tasks.filter(t => t.type === 'maintenance-task').length,
+ 'auto-task': tasks.filter(t => t.type === 'auto-task').length
+ }
+ };
+
+ return stats;
+}
+
+/**
+ * 清理已完成任务(保留最近 50 条)
+ */
+function cleanup() {
+ const queue = loadQueue();
+ const completed = queue.tasks.filter(t =>
+ t.status === 'completed' || t.status === 'failed'
+ );
+
+ if (completed.length > 50) {
+ const keep = completed
+ .sort((a, b) => (b.completed_at || b.failed_at || '').localeCompare(a.completed_at || a.failed_at || ''))
+ .slice(0, 50);
+ const keepIds = new Set(keep.map(t => t.task_id));
+ const active = queue.tasks.filter(t =>
+ t.status === 'pending' || t.status === 'running'
+ );
+ queue.tasks = [...active, ...keep];
+ saveQueue(queue);
+ console.log(`🧹 清理完成,保留 ${queue.tasks.length} 条任务`);
+ }
+}
+
+// CLI 入口
+if (require.main === module) {
+ const cmd = process.argv[2] || 'status';
+
+ switch (cmd) {
+ case 'status': {
+ const s = status();
+ console.log('📊 任务队列状态:');
+ console.log(` 总计: ${s.total}`);
+ console.log(` 待处理: ${s.pending}`);
+ console.log(` 执行中: ${s.running}`);
+ console.log(` 已完成: ${s.completed}`);
+ console.log(` 失败: ${s.failed}`);
+ console.log(` 更新时间: ${s.last_updated || '无'}`);
+ if (s.total > 0) {
+ console.log(' 任务类型分布:');
+ for (const [type, count] of Object.entries(s.by_type)) {
+ if (count > 0) console.log(` ${type}: ${count}`);
+ }
+ }
+ break;
+ }
+ case 'run': {
+ const task = dequeue();
+ if (task) {
+ console.log(`🚀 取出任务: ${task.task_id}`);
+ } else {
+ console.log('✅ 队列为空,无待执行任务');
+ }
+ break;
+ }
+ case 'cleanup': {
+ cleanup();
+ break;
+ }
+ default:
+ console.log('用法: node core/task-queue [status|run|cleanup]');
+ }
+}
+
+module.exports = { loadQueue, enqueue, enqueueBatch, dequeue, complete, fail, status, cleanup };
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
+ });
+});
diff --git a/docs/HoloLake-Era-OS-Modules.md b/docs/HoloLake-Era-OS-Modules.md
index da5c9c05..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 01:56 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 01:56 UTC |
+| 文档更新时间 | 2026-03-13 13:38 UTC |
---
diff --git a/docs/execution-layer-map.md b/docs/execution-layer-map.md
new file mode 100644
index 00000000..c0f2c8f2
--- /dev/null
+++ b/docs/execution-layer-map.md
@@ -0,0 +1,95 @@
+# 执行层结构地图 — execution-layer-map.md
+
+> 铸渊执行层自动开发循环升级产物 · TCS-0002∞
+> 生成时间:2026-03-14 · v5.1
+
+---
+
+## 执行层系统结构
+
+```
+零点原核(语言观察层)
+ ↓
+数字地球主控台(Notion 主脑 / 曜冥)
+ ↓
+系统广播
+ ↓
+仓库执行层(铸渊)
+ ↓
+自动开发循环
+ ↓
+自动化执行系统
+```
+
+---
+
+## 核心执行模块
+
+| 模块 | 路径 | 职责 |
+|------|------|------|
+| 上下文加载 | `core/context-loader/index.js` | 执行前加载系统上下文与身份认知 |
+| 广播监听 | `core/broadcast-listener/index.js` | 监听并解析 Notion 广播为可执行任务 |
+| 任务队列 | `core/task-queue/index.js` | 任务入队、调度、优先级管理、类型分类 |
+| 系统自检 | `core/system-check/index.js` | 仓库结构完整性检查 + 自动任务生成 |
+| 执行同步 | `core/execution-sync/index.js` | 生成执行状态报告并同步到 Notion |
+
+---
+
+## 自动化模块
+
+| 工作流 | 触发方式 | 职责 |
+|--------|----------|------|
+| `daily-maintenance.yml` | cron `0 2 * * *` | 每日巡检与 system-health 更新 |
+| `zhuyuan-daily-selfcheck.yml` | cron `0 0 * * *` | 铸渊每日自检 |
+| `execution-sync.yml` | cron `0 3 * * *` | 执行层状态同步到 Notion |
+| `notion-heartbeat.yml` | cron `*/5 * * * *` | 工单心跳监控 |
+| `brain-sync.yml` | workflow_dispatch | 大脑数据同步 |
+
+---
+
+## Notion 连接模块
+
+| 模块 | 路径 | 方向 | 职责 |
+|------|------|------|------|
+| Notion 同步 | `connectors/notion-sync/index.js` | 双向 | 广播拉取 / 日志写回 / 状态同步 |
+| 模型路由 | `connectors/model-router/index.js` | 出站 | 统一模型调用入口 |
+| Notion 桥接 | `scripts/notion-bridge.js` | 上行 | SYSLOG 上报 / 变更同步 |
+| 信号桥 | `scripts/notion-signal-bridge.js` | 下行 | 工单轮询 / 信号执行 |
+| 心跳监控 | `scripts/notion-heartbeat.js` | 双向 | 超时检测 / 自动重试 |
+
+---
+
+## 任务执行模块
+
+| 脚本 | 职责 |
+|------|------|
+| `scripts/process-broadcasts.js` | 处理广播(JSON 规则 + MD 成长日志) |
+| `scripts/daily-check.js` | 文件完整性 / HLI 覆盖率 / Schema 验证 |
+| `scripts/zhuyuan-daily-selfcheck.js` | 大脑文件验证 / FAQ 去重 / 记忆修剪 |
+| `scripts/distribute-broadcasts.js` | 广播分发 |
+| `scripts/generate-repo-map.js` | 仓库结构索引生成 |
+| `scripts/generate-system-health.js` | 系统健康报告生成 |
+
+---
+
+## 执行闭环(自动开发循环 v5.1)
+
+```
+context-loader(上下文加载)
+ ↓
+system-check(自检 + 自动任务生成)
+ ↓
+execution-sync(状态采集与报告)
+ ↓
+broadcast-listener(广播监听)
+ ↓
+task-queue(任务排队 · 类型: system/dev/maintenance/auto)
+ ↓
+执行器运行
+ ↓
+connectors/notion-sync(状态回写)
+ ↓
+Notion 主脑更新
+ ↓
+生成下一任务(自动开发循环)
+```
diff --git a/docs/execution-status.md b/docs/execution-status.md
new file mode 100644
index 00000000..141d99f0
--- /dev/null
+++ b/docs/execution-status.md
@@ -0,0 +1,61 @@
+# 执行层状态报告 — execution-status.md
+
+> 铸渊执行层自动生成 · TCS-0002∞
+> 更新时间:2026-03-14 14:58:44+08:00
+
+---
+
+## 系统概览
+
+| 指标 | 状态 |
+|------|------|
+| 系统版本 | v5.1 |
+| 执行层状态 | ✅ Stable |
+| Notion 桥接 | ✅ Active |
+| 执行同步 | ✅ Enabled |
+| 工作流数量 | 43 |
+
+---
+
+## 核心模块状态
+
+| 模块 | 路径 | 状态 |
+|------|------|------|
+| broadcast-listener | `core/broadcast-listener/index.js` | ✅ Enabled |
+| task-queue | `core/task-queue/index.js` | ✅ Enabled |
+| system-check | `core/system-check/index.js` | ✅ Enabled |
+| execution-sync | `core/execution-sync/index.js` | ✅ Enabled |
+| context-loader | `core/context-loader/index.js` | ✅ Enabled |
+
+---
+
+## 连接器状态
+
+| 连接器 | 路径 | 状态 |
+|--------|------|------|
+| notion-sync | `connectors/notion-sync/index.js` | ✅ Enabled |
+| model-router | `connectors/model-router/index.js` | ✅ Enabled |
+
+---
+
+## 任务队列状态
+
+| 指标 | 数量 |
+|------|------|
+| 总计 | 0 |
+| 待处理 | 0 |
+| 执行中 | 0 |
+| 已完成 | 0 |
+| 失败 | 0 |
+
+---
+
+## 执行闭环
+
+```
+context-loader → system-check → execution-sync → 状态报告
+ ↓
+broadcast-listener → task-queue → 任务执行
+ ↓
+notion-sync → Notion 主脑更新
+```
diff --git a/docs/notion-bridge-map.md b/docs/notion-bridge-map.md
new file mode 100644
index 00000000..21acc5d0
--- /dev/null
+++ b/docs/notion-bridge-map.md
@@ -0,0 +1,69 @@
+# Notion 桥接地图 — notion-bridge-map.md
+
+> 铸渊执行层系统升级产物 · TCS-0002∞
+> 生成时间:2026-03-14
+
+---
+
+## 连接总览
+
+```
+Notion 主脑(数字地球主控台)
+ ↕
+connectors/notion-sync(双向同步模块)
+ ↕
+仓库执行层(铸渊)
+```
+
+---
+
+## API 调用路径
+
+| 脚本 | 方向 | Notion 数据库 | 环境变量 |
+|------|------|---------------|----------|
+| `scripts/notion-bridge.js` | 仓库 → Notion | SYSLOG Inbox / Changes Log | `NOTION_TOKEN`, `SYSLOG_DB_ID`, `CHANGES_DB_ID` |
+| `scripts/notion-signal-bridge.js` | Notion → 仓库 | 工单数据库 / 信号日志 | `NOTION_API_TOKEN`, `WORKORDER_DB_ID`, `SIGNAL_LOG_DB_ID` |
+| `scripts/notion-heartbeat.js` | Notion ↔ 仓库 | 工单数据库 | `NOTION_TOKEN`, `NOTION_TICKET_DB_ID` |
+| `scripts/brain-bridge-sync.js` | Notion ↔ 仓库 | 大脑同步 | `NOTION_TOKEN` |
+| `connectors/notion-sync/index.js` | 双向统一接口 | 广播 / 日志 | `NOTION_TOKEN`, `BROADCAST_DB_ID` |
+
+---
+
+## 数据同步逻辑
+
+### 仓库 → Notion(上行)
+
+1. **SYSLOG 上报**:`syslog-inbox/` → `notion-bridge.js` → Notion SYSLOG 数据库
+2. **变更同步**:Git commits/PRs → `notion-bridge.js` → Notion Changes Log
+3. **执行日志**:任务完成 → `connectors/notion-sync` → Notion 广播数据库
+4. **心跳回写**:工单状态 → `notion-heartbeat.js` → Notion 工单数据库
+
+### Notion → 仓库(下行)
+
+1. **广播监听**:Notion 广播 → `connectors/notion-sync` → `core/broadcast-listener`
+2. **工单派发**:Notion 工单 → `notion-signal-bridge.js` → 本地任务执行
+3. **人格唤醒**:Notion 触发 → `persona-invoke.yml` → 人格体执行
+
+---
+
+## 连接方式
+
+| 连接类型 | 实现 | 说明 |
+|----------|------|------|
+| Notion API | HTTPS REST | Notion API v2022-06-28 |
+| 轮询机制 | Cron / workflow_dispatch | 定时拉取 + 手动触发 |
+| 心跳监控 | 5 分钟间隔 Cron | 超时自动重试(最多 3 次) |
+| 信号桥 | 工单状态驱动 | `已发送` → 执行 → `已完成` |
+
+---
+
+## 关联工作流
+
+| 工作流 | 触发方式 | 功能 |
+|--------|----------|------|
+| `notion-heartbeat.yml` | cron `*/5 * * * *` | 工单心跳监控 |
+| `bridge-syslog-to-notion.yml` | push (syslog-inbox) | SYSLOG 上报 |
+| `bridge-changes-to-notion.yml` | push (main) | 变更同步 |
+| `brain-sync.yml` | workflow_dispatch | 大脑数据同步 |
+| `process-notion-orders.yml` | workflow_dispatch | 工单处理 |
+| `persona-invoke.yml` | workflow_dispatch | 人格唤醒执行 |
diff --git a/docs/repo-structure-map.md b/docs/repo-structure-map.md
new file mode 100644
index 00000000..86b82693
--- /dev/null
+++ b/docs/repo-structure-map.md
@@ -0,0 +1,95 @@
+# 仓库结构地图 — repo-structure-map.md
+
+> 铸渊执行层系统升级产物 · TCS-0002∞
+> 生成时间:2026-03-14
+
+---
+
+## 系统总览
+
+```
+零点原核(语言观察层)
+ ↓
+数字地球主控台(Notion 主脑)
+ ↓
+系统广播
+ ↓
+仓库执行层(铸渊)
+ ↓
+自动化执行系统
+ ↓
+开发者 / 模型
+```
+
+---
+
+## 核心模块
+
+| 目录 | 功能 | 类型 |
+|------|------|------|
+| `core/broadcast-listener` | 广播监听与解析 | 核心模块 |
+| `core/task-queue` | 任务队列与调度 | 核心模块 |
+| `core/system-check` | 仓库自检系统 | 核心模块 |
+| `brain/` | 执行层大脑索引 | 知识索引 |
+| `src/` | HLI 接口服务 | 应用服务 |
+
+---
+
+## 自动化模块
+
+| 目录 | 功能 | 类型 |
+|------|------|------|
+| `.github/workflows/` | GitHub Actions 工作流 | CI/CD |
+| `scripts/` | 脚本工具集 | 自动化 |
+| `.github/broadcasts/` | 广播入站队列 | 信号管道 |
+
+---
+
+## Notion 连接模块
+
+| 目录 / 文件 | 功能 | 类型 |
+|------|------|------|
+| `connectors/notion-sync` | Notion 双向同步 | 连接器 |
+| `connectors/model-router` | 模型调用路由 | 连接器 |
+| `scripts/notion-bridge.js` | Notion 数据桥接 | 桥接脚本 |
+| `scripts/notion-signal-bridge.js` | Notion 信号桥 | 桥接脚本 |
+| `scripts/notion-heartbeat.js` | Notion 心跳监控 | 监控脚本 |
+
+---
+
+## 开发模块
+
+| 目录 | 功能 |
+|------|------|
+| `backend/` | Express 后端服务 (port 3000) |
+| `backend-integration/` | AI Chat API 代理 (port 3721) |
+| `persona-studio/` | 人格工作室 (port 3002) |
+| `frontend/` | 前端组件 |
+| `m01-login` ~ `m18-health-check` | 功能模块集 |
+
+---
+
+## 数据与日志
+
+| 目录 | 功能 |
+|------|------|
+| `broadcasts/` | 广播存档 |
+| `broadcasts-outbox/` | 广播发件箱 |
+| `syslog/` | 系统日志 |
+| `syslog-inbox/` | 系统日志入站 |
+| `syslog-processed/` | 已处理日志 |
+| `reports/` | 报告存档 |
+| `persona-telemetry/` | 人格遥测数据 |
+| `collaboration-logs/` | 协作日志 |
+
+---
+
+## PM2 服务映射
+
+| 服务名 | 入口 | 端口 |
+|--------|------|------|
+| guanghulab | `src/index.js` | 3001 |
+| guanghulab-backend | `backend/server.js` | 3000 |
+| guanghulab-proxy | `backend-integration/api-proxy.js` | 3721 |
+| guanghulab-ws | `status-board/mock-ws-server.js` | 8080 |
+| persona-studio | `persona-studio/backend/server.js` | 3002 |
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/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/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 · 用户频道引擎
+ 光湖频道 · 动态渲染引擎
+
+
-
-
+
+
+
+
+
+
-
-
- ✨ 加载中...
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+