feat: AGE OS 跨平台全链路自动化闭环系统 GitHub 端落地实施
This commit is contained in:
parent
d3c834ad5d
commit
3503923a4a
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||
"rules_version": "v1.0",
|
||||
"last_updated": "2026-03-06T08:51:24.831Z",
|
||||
"last_updated": "2026-03-07T04:45:55.670Z",
|
||||
"wake_protocol_version": "v1.0",
|
||||
"wake_triggers": [
|
||||
"我是冰朔",
|
||||
|
|
@ -23,6 +23,12 @@
|
|||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"type": "psp_inspection",
|
||||
"description": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
|
||||
"by": "铸渊PSP巡检"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-03-05T10:30:46.894Z",
|
||||
"type": "ci_run",
|
||||
|
|
@ -70,4 +76,4 @@
|
|||
"run_id": "22756140950"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -37,3 +37,10 @@
|
|||
- 累计自检次数:4
|
||||
- 累计CI运行:1次
|
||||
- HLI覆盖率:17.6%
|
||||
|
||||
## 2026-03-07 · 每日自检
|
||||
- 大脑文件完整性:⚠️ 缺失 identity.md, routing-map.json, responsibility.md, decision-log.md
|
||||
- 知识库条目:4条
|
||||
- 累计自检次数:5
|
||||
- 累计CI运行:1次
|
||||
- HLI覆盖率:17.6%
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体",
|
||||
"rules_version": "v1.0",
|
||||
"last_updated": "2026-03-07T00:54:06.899Z",
|
||||
"last_updated": "2026-03-07T04:46:02.610Z",
|
||||
"wake_protocol_version": "v1.0",
|
||||
"wake_triggers": [
|
||||
"我是冰朔",
|
||||
|
|
@ -12,9 +12,15 @@
|
|||
],
|
||||
"founder": "冰朔(Bīng Shuò)",
|
||||
"hli_coverage": "17.6%",
|
||||
"total_selfchecks": 4,
|
||||
"total_selfchecks": 5,
|
||||
"total_ci_runs": 1,
|
||||
"recent_events": [
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"type": "daily_selfcheck",
|
||||
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
|
||||
"by": "铸渊自检"
|
||||
},
|
||||
{
|
||||
"date": "2026-03-07",
|
||||
"type": "daily_selfcheck",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
name: 铸渊 · ESP 邮件信号处理器
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/30 * * * *' # 每30分钟执行一次
|
||||
workflow_dispatch: # 手动触发
|
||||
|
||||
jobs:
|
||||
esp-process:
|
||||
name: 📡 ESP 信号处理
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 安装依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 处理 GL-CMD 邮件信号
|
||||
env:
|
||||
EMAIL_ADDRESS: ${{ secrets.EMAIL_ADDRESS }}
|
||||
EMAIL_APP_PASSWORD: ${{ secrets.EMAIL_APP_PASSWORD }}
|
||||
run: node scripts/esp-email-processor.js
|
||||
|
||||
- name: 提交信号日志
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add signal-log/ notion-push/ dev-nodes/
|
||||
git diff --cached --quiet || git commit -m "📡 ESP 信号处理 · $(date +%Y-%m-%dT%H:%M)"
|
||||
git push
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
name: 铸渊 · PSP 分身巡检
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00
|
||||
workflow_dispatch: # 手动触发
|
||||
|
||||
jobs:
|
||||
psp-inspect:
|
||||
name: 🔍 铸渊 PSP 每日巡检
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 安装依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 执行 PSP 巡检(5项检查)
|
||||
env:
|
||||
EMAIL_ADDRESS: ${{ secrets.EMAIL_ADDRESS }}
|
||||
EMAIL_APP_PASSWORD: ${{ secrets.EMAIL_APP_PASSWORD }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: node scripts/psp-inspection.js
|
||||
|
||||
- name: 提交巡检结果
|
||||
run: |
|
||||
git config user.name "铸渊 (ZhùYuān)"
|
||||
git config user.email "zhuyuan@guanghulab.com"
|
||||
git add signal-log/ dev-nodes/ notion-push/ .github/brain/memory.json
|
||||
git diff --cached --quiet || git commit -m "🔍 铸渊PSP巡检 · $(date +%Y-%m-%d)"
|
||||
git push
|
||||
Binary file not shown.
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-001",
|
||||
"name": "页页",
|
||||
"emoji": "🖥️",
|
||||
"modules": ["backend-integration"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-001",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-002",
|
||||
"name": "肥猫",
|
||||
"emoji": "🦁",
|
||||
"modules": ["m01-login", "m03-personality"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-002",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-003",
|
||||
"name": "燕樊",
|
||||
"emoji": "🌸",
|
||||
"modules": ["m07-dialogue-ui", "m10-cloud", "m15-cloud-drive"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-003",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-004",
|
||||
"name": "之之",
|
||||
"emoji": "🤖",
|
||||
"modules": ["dingtalk-bot"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-004",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-005",
|
||||
"name": "小草莓",
|
||||
"emoji": "🍓",
|
||||
"modules": ["m12-kanban", "status-board"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-005",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-009",
|
||||
"name": "花尔",
|
||||
"emoji": "🌺",
|
||||
"modules": ["m05-user-center"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-009",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-010",
|
||||
"name": "桔子",
|
||||
"emoji": "🍊",
|
||||
"modules": ["m06-ticket", "m11-module"],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-010",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dev_id": "DEV-011",
|
||||
"name": "匆匆那年",
|
||||
"emoji": "🌙",
|
||||
"modules": [],
|
||||
"pat_configured": false,
|
||||
"network_scheme": "layer1_https",
|
||||
"created_at": "2026-03-06T23:00:00+08:00",
|
||||
"notes": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"dev_id": "DEV-011",
|
||||
"last_seen": null,
|
||||
"last_push": null,
|
||||
"active_module": null,
|
||||
"pending_broadcasts": 0,
|
||||
"signal_count": 0,
|
||||
"updated_at": "2026-03-06T23:00:00+08:00"
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#!/bin/bash
|
||||
# gl-push.sh — 光湖智能上传脚本 · 三层网络降级方案
|
||||
# 用法: bash gl-push.sh "提交信息"
|
||||
# 要求: git 已配置,PAT token 已通过 git config credential.helper store 存储
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="https://github.com/qinfendebingshuo/guanghulab.git"
|
||||
PROXY_URL="https://ghproxy.com/https://github.com/qinfendebingshuo/guanghulab.git"
|
||||
MSG="${1:-"📤 开发者上传 $(date +%Y-%m-%dT%H:%M)"}"
|
||||
|
||||
echo "🐙 光湖代码上传中..."
|
||||
git add .
|
||||
git commit -m "$MSG" 2>/dev/null || echo " ℹ️ 无新提交,直接推送"
|
||||
|
||||
# ── 层级1:直连 HTTPS ──────────────────────────────────────────────────────
|
||||
echo "🔗 [层级1] 尝试直连 GitHub..."
|
||||
if git push origin main 2>/dev/null; then
|
||||
echo "✅ 直连上传成功!铸渊已接收。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── 层级2:ghproxy 镜像加速 ───────────────────────────────────────────────
|
||||
echo "⚠️ 直连失败,切换镜像通道..."
|
||||
git remote set-url origin "$PROXY_URL"
|
||||
if git push origin main 2>/dev/null; then
|
||||
echo "✅ 镜像通道上传成功!铸渊已接收。"
|
||||
git remote set-url origin "$REPO_URL"
|
||||
exit 0
|
||||
fi
|
||||
git remote set-url origin "$REPO_URL"
|
||||
|
||||
# ── 层级3:SSH ────────────────────────────────────────────────────────────
|
||||
echo "⚠️ 镜像也失败,尝试 SSH 通道..."
|
||||
SSH_URL="git@github.com:qinfendebingshuo/guanghulab.git"
|
||||
git remote set-url origin "$SSH_URL"
|
||||
if git push origin main; then
|
||||
echo "✅ SSH 通道上传成功!铸渊已接收。"
|
||||
git remote set-url origin "$REPO_URL"
|
||||
exit 0
|
||||
fi
|
||||
git remote set-url origin "$REPO_URL"
|
||||
|
||||
# ── 全部失败 ──────────────────────────────────────────────────────────────
|
||||
echo "❌ 三层网络均不通,请联系知秋处理。"
|
||||
echo " 可尝试手动命令: git push git@github.com:qinfendebingshuo/guanghulab.git main"
|
||||
exit 1
|
||||
|
|
@ -8,7 +8,9 @@
|
|||
"name": "nextjs",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"imapflow": "^1.2.12",
|
||||
"next": "15.3.8",
|
||||
"nodemailer": "^8.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
|
|
@ -1644,6 +1646,12 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pinojs/redact": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
||||
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
|
@ -2161,6 +2169,17 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@zone-eu/mailsplit": {
|
||||
"version": "5.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz",
|
||||
"integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==",
|
||||
"license": "(MIT OR EUPL-1.1+)",
|
||||
"dependencies": {
|
||||
"libbase64": "1.3.0",
|
||||
"libmime": "5.3.7",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
|
|
@ -2230,6 +2249,15 @@
|
|||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
|
|
@ -2955,6 +2983,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encoding-japanese": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz",
|
||||
"integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
|
|
@ -3282,6 +3319,39 @@
|
|||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/imapflow": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.2.12.tgz",
|
||||
"integrity": "sha512-UX8qCKXZk2xExe/x8KPTSbhROdtUGP13bSLSjT9Sb3YwGuryD4aFNlGhbWBW5B1GtgHMRxVv9yvl61RqXgIQtQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zone-eu/mailsplit": "5.4.8",
|
||||
"encoding-japanese": "2.2.0",
|
||||
"iconv-lite": "0.7.2",
|
||||
"libbase64": "1.3.0",
|
||||
"libmime": "5.3.7",
|
||||
"libqp": "2.1.1",
|
||||
"nodemailer": "8.0.1",
|
||||
"pino": "10.3.1",
|
||||
"socks": "2.8.7"
|
||||
}
|
||||
},
|
||||
"node_modules/import-local": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||
|
|
@ -3331,6 +3401,15 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
|
|
@ -4250,6 +4329,42 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/libbase64": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz",
|
||||
"integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/libmime": {
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
|
||||
"integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encoding-japanese": "2.2.0",
|
||||
"iconv-lite": "0.6.3",
|
||||
"libbase64": "1.3.0",
|
||||
"libqp": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/libmime/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libqp": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz",
|
||||
"integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
|
|
@ -4730,6 +4845,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz",
|
||||
"integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
|
@ -4753,6 +4877,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
|
@ -4940,6 +5073,43 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
|
||||
"integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pinojs/redact": "^0.4.0",
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"pino-std-serializers": "^7.0.0",
|
||||
"process-warning": "^5.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"thread-stream": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
|
||||
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-std-serializers": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
|
||||
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
|
|
@ -5020,6 +5190,22 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/process-warning": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
|
||||
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
|
|
@ -5051,6 +5237,12 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
|
|
@ -5079,6 +5271,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/real-require": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
|
@ -5143,6 +5344,21 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
|
|
@ -5266,6 +5482,39 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
||||
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ip-address": "^10.0.1",
|
||||
"smart-buffer": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
|
||||
"integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
|
@ -5296,6 +5545,15 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
|
@ -5625,6 +5883,18 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
|
||||
"integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"real-require": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
|
|
|
|||
|
|
@ -14,10 +14,14 @@
|
|||
"brain:sync": "node scripts/process-broadcasts.js",
|
||||
"brain:daily-check": "node scripts/daily-check.js",
|
||||
"brain:update-memory": "node scripts/update-memory.js",
|
||||
"broadcast:distribute": "node scripts/distribute-broadcasts.js"
|
||||
"broadcast:distribute": "node scripts/distribute-broadcasts.js",
|
||||
"esp:process": "node scripts/esp-email-processor.js",
|
||||
"psp:inspect": "node scripts/psp-inspection.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"imapflow": "^1.2.12",
|
||||
"next": "15.3.8",
|
||||
"nodemailer": "^8.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,360 @@
|
|||
// scripts/esp-email-processor.js
|
||||
// ESP 邮件信号协议 · GitHub 端处理器
|
||||
// 功能:读取 Gmail IMAP → 解析 [GL-CMD] 信号 → 执行指令 → 写 signal-log → 发 [GL-ACK]
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { ImapFlow } = require('imapflow');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
// ── 配置 ────────────────────────────────────────────────────────────────────
|
||||
const EMAIL_ADDRESS = process.env.EMAIL_ADDRESS;
|
||||
const EMAIL_PASSWORD = process.env.EMAIL_APP_PASSWORD;
|
||||
|
||||
if (!EMAIL_ADDRESS || !EMAIL_PASSWORD) {
|
||||
console.error('❌ 缺少环境变量 EMAIL_ADDRESS 或 EMAIL_APP_PASSWORD');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const SIGNAL_LOG_DIR = path.join(__dirname, '../signal-log');
|
||||
const SIGNAL_INDEX_PATH = path.join(SIGNAL_LOG_DIR, 'index.json');
|
||||
const NOTION_PUSH_DIR = path.join(__dirname, '../notion-push/pending');
|
||||
const DEV_NODES_DIR = path.join(__dirname, '../dev-nodes');
|
||||
|
||||
// ── 信号 ID 生成 ──────────────────────────────────────────────────────────
|
||||
function generateSignalId() {
|
||||
const now = new Date();
|
||||
const ymd = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const index = loadSignalIndex();
|
||||
const seq = String(index.total_count + 1).padStart(3, '0');
|
||||
return `SIG-${ymd}-${seq}`;
|
||||
}
|
||||
|
||||
// ── 信号索引读写 ──────────────────────────────────────────────────────────
|
||||
function loadSignalIndex() {
|
||||
if (!fs.existsSync(SIGNAL_INDEX_PATH)) {
|
||||
return { last_updated: new Date().toISOString(), total_count: 0, signals: [] };
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(SIGNAL_INDEX_PATH, 'utf8'));
|
||||
}
|
||||
|
||||
function saveSignalIndex(index) {
|
||||
index.last_updated = new Date().toISOString();
|
||||
fs.writeFileSync(SIGNAL_INDEX_PATH, JSON.stringify(index, null, 2));
|
||||
}
|
||||
|
||||
// ── 写入信号日志文件 ──────────────────────────────────────────────────────
|
||||
function writeSignalLog(signal) {
|
||||
const dateStr = signal.timestamp.slice(0, 7); // "YYYY-MM"
|
||||
const monthDir = path.join(SIGNAL_LOG_DIR, dateStr);
|
||||
fs.mkdirSync(monthDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(monthDir, `${signal.signal_id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(signal, null, 2));
|
||||
|
||||
const index = loadSignalIndex();
|
||||
index.total_count += 1;
|
||||
index.signals.unshift({
|
||||
signal_id: signal.signal_id,
|
||||
trace_id: signal.trace_id,
|
||||
type: signal.signal_type,
|
||||
timestamp: signal.timestamp,
|
||||
summary: signal.summary,
|
||||
related_dev: signal.related_dev || null,
|
||||
file: `${dateStr}/${signal.signal_id}.json`
|
||||
});
|
||||
saveSignalIndex(index);
|
||||
|
||||
console.log(`📝 信号已写入: ${signal.signal_id}`);
|
||||
}
|
||||
|
||||
// ── 写入 notion-push/pending ──────────────────────────────────────────────
|
||||
function pushToNotion(signal) {
|
||||
fs.mkdirSync(NOTION_PUSH_DIR, { recursive: true });
|
||||
const filePath = path.join(NOTION_PUSH_DIR, `${signal.signal_id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(signal, null, 2));
|
||||
console.log(`📤 已写入 notion-push/pending: ${signal.signal_id}`);
|
||||
}
|
||||
|
||||
// ── SMTP 发送 ACK 邮件 ────────────────────────────────────────────────────
|
||||
async function sendAckEmail(originalSignal, ackSignalId, result) {
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: { user: EMAIL_ADDRESS, pass: EMAIL_PASSWORD }
|
||||
});
|
||||
|
||||
const ackPayload = {
|
||||
signal_type: 'GL-ACK',
|
||||
trace_id: originalSignal.trace_id,
|
||||
ack_signal_id: ackSignalId,
|
||||
original_cmd_id: originalSignal.signal_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
original_command: originalSignal.command,
|
||||
result,
|
||||
message: `指令 ${originalSignal.command} 执行${result === '成功' ? '完成' : '失败'}`
|
||||
};
|
||||
|
||||
await transporter.sendMail({
|
||||
from: EMAIL_ADDRESS,
|
||||
to: EMAIL_ADDRESS,
|
||||
subject: `[GL-ACK] ${originalSignal.trace_id} | ${originalSignal.command} ${result}`,
|
||||
text: JSON.stringify(ackPayload, null, 2)
|
||||
});
|
||||
|
||||
console.log(`📧 [GL-ACK] 已发送: ${ackSignalId}`);
|
||||
return ackPayload;
|
||||
}
|
||||
|
||||
// ── 指令执行器 ────────────────────────────────────────────────────────────
|
||||
async function executeCommand(signal) {
|
||||
const { command, payload } = signal;
|
||||
console.log(`⚙️ 执行指令: ${command}`);
|
||||
|
||||
switch (command) {
|
||||
case 'sync_broadcast': {
|
||||
// 将 broadcasts-outbox/ 中的广播同步到对应 dev-nodes/
|
||||
const outboxDir = path.join(__dirname, '../broadcasts-outbox');
|
||||
if (!fs.existsSync(outboxDir)) {
|
||||
return { result: '失败', detail: 'broadcasts-outbox 目录不存在' };
|
||||
}
|
||||
const devDirs = fs.readdirSync(outboxDir).filter(f => f.startsWith('DEV-'));
|
||||
let synced = 0;
|
||||
for (const devId of devDirs) {
|
||||
const srcDir = path.join(outboxDir, devId);
|
||||
const destDir = path.join(DEV_NODES_DIR, devId);
|
||||
if (!fs.existsSync(destDir)) continue;
|
||||
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of files) {
|
||||
const src = path.join(srcDir, file);
|
||||
const dest = path.join(destDir, file);
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.copyFileSync(src, dest);
|
||||
synced++;
|
||||
}
|
||||
}
|
||||
// 更新 status.json 中的 pending_broadcasts
|
||||
const statusPath = path.join(destDir, 'status.json');
|
||||
if (fs.existsSync(statusPath)) {
|
||||
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
||||
status.pending_broadcasts = files.length;
|
||||
status.updated_at = new Date().toISOString();
|
||||
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
||||
}
|
||||
}
|
||||
return { result: '成功', detail: `已同步 ${synced} 个广播文件` };
|
||||
}
|
||||
|
||||
case 'check_syslog': {
|
||||
const syslogInbox = path.join(__dirname, '../syslog-inbox');
|
||||
const files = fs.existsSync(syslogInbox)
|
||||
? fs.readdirSync(syslogInbox).filter(f => f.endsWith('.json'))
|
||||
: [];
|
||||
const summary = `syslog-inbox 待处理: ${files.length} 条`;
|
||||
pushToNotion({
|
||||
signal_id: generateSignalId(),
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
signal_type: 'GL-DATA',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: null,
|
||||
related_module: null,
|
||||
summary,
|
||||
payload: { syslog_pending: files.length, files }
|
||||
});
|
||||
return { result: '成功', detail: summary };
|
||||
}
|
||||
|
||||
case 'update_status': {
|
||||
const { dev_id, updates } = payload || {};
|
||||
if (!dev_id) return { result: '失败', detail: '缺少 dev_id' };
|
||||
const statusPath = path.join(DEV_NODES_DIR, dev_id, 'status.json');
|
||||
if (!fs.existsSync(statusPath)) {
|
||||
return { result: '失败', detail: `${dev_id} status.json 不存在` };
|
||||
}
|
||||
let status;
|
||||
try {
|
||||
status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
||||
} catch (err) {
|
||||
return { result: '失败', detail: `${dev_id} status.json 解析失败: ${err.message}` };
|
||||
}
|
||||
Object.assign(status, updates, { updated_at: new Date().toISOString() });
|
||||
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
||||
return { result: '成功', detail: `${dev_id} 状态已更新` };
|
||||
}
|
||||
|
||||
default:
|
||||
return { result: '失败', detail: `未知指令: ${command}` };
|
||||
}
|
||||
}
|
||||
|
||||
// ── 解析邮件正文中的 JSON 信号 ────────────────────────────────────────────
|
||||
function parseSignalFromEmail(subject, body) {
|
||||
if (!subject.includes('[GL-CMD]')) return null;
|
||||
try {
|
||||
// 提取第一个完整 JSON 对象(跳过邮件头中的任何前缀内容)
|
||||
const jsonMatch = body.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) return null;
|
||||
const parsed = JSON.parse(jsonMatch[0]);
|
||||
if (!parsed.signal_type || !parsed.trace_id || !parsed.command) return null;
|
||||
return parsed;
|
||||
} catch (err) {
|
||||
console.log(`⚠️ 邮件 JSON 解析失败: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 主流程 ────────────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
console.log('═══════════════════════════════════════════════');
|
||||
console.log('📡 ESP 邮件信号处理器启动');
|
||||
console.log(` 时间: ${new Date().toISOString()}`);
|
||||
console.log('═══════════════════════════════════════════════\n');
|
||||
|
||||
const client = new ImapFlow({
|
||||
host: 'imap.gmail.com',
|
||||
port: 993,
|
||||
secure: true,
|
||||
auth: { user: EMAIL_ADDRESS, pass: EMAIL_PASSWORD },
|
||||
logger: false
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
let processed = 0;
|
||||
let errors = 0;
|
||||
|
||||
try {
|
||||
const lock = await client.getMailboxLock('INBOX');
|
||||
try {
|
||||
// 搜索未读的 [GL-CMD] 邮件
|
||||
const messages = await client.search({ seen: false, subject: '[GL-CMD]' });
|
||||
|
||||
if (!messages || messages.length === 0) {
|
||||
console.log('📭 无待处理 [GL-CMD] 邮件');
|
||||
} else {
|
||||
console.log(`📬 发现 ${messages.length} 封 [GL-CMD] 邮件`);
|
||||
|
||||
for await (const msg of client.fetch(messages, { envelope: true, source: true })) {
|
||||
const subject = msg.envelope?.subject || '';
|
||||
const rawBody = msg.source?.toString('utf8') || '';
|
||||
|
||||
// 提取 JSON 正文(跳过邮件头)
|
||||
const bodyStart = rawBody.indexOf('{');
|
||||
const body = bodyStart >= 0 ? rawBody.slice(bodyStart) : '';
|
||||
|
||||
const signal = parseSignalFromEmail(subject, body);
|
||||
if (!signal) {
|
||||
console.log(`⚠️ 跳过无效邮件: ${subject}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`\n📩 处理信号: ${signal.trace_id} · ${signal.command}`);
|
||||
|
||||
// 生成信号 ID(如邮件中未提供)
|
||||
if (!signal.signal_id) signal.signal_id = generateSignalId();
|
||||
|
||||
// 写入 signal-log
|
||||
const cmdSignal = {
|
||||
signal_id: signal.signal_id,
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: signal.timestamp || new Date().toISOString(),
|
||||
signal_type: 'GL-CMD',
|
||||
direction: 'Notion→GitHub',
|
||||
sender: signal.sender || '霜砚',
|
||||
receiver: '铸渊',
|
||||
related_dev: signal.payload?.dev_id || null,
|
||||
related_module: signal.payload?.module || null,
|
||||
summary: `执行指令 ${signal.command}`,
|
||||
payload: signal.payload || {},
|
||||
result: null,
|
||||
ack_signal_id: null
|
||||
};
|
||||
writeSignalLog(cmdSignal);
|
||||
|
||||
// 执行指令
|
||||
let execResult;
|
||||
try {
|
||||
execResult = await executeCommand(signal);
|
||||
} catch (err) {
|
||||
execResult = { result: '失败', detail: err.message };
|
||||
}
|
||||
|
||||
cmdSignal.result = execResult.result;
|
||||
|
||||
// 发送 ACK 邮件并写入 ACK 信号日志
|
||||
const ackSignalId = generateSignalId();
|
||||
cmdSignal.ack_signal_id = ackSignalId;
|
||||
|
||||
try {
|
||||
const ackPayload = await sendAckEmail(
|
||||
{ ...signal, signal_id: cmdSignal.signal_id },
|
||||
ackSignalId,
|
||||
execResult.result
|
||||
);
|
||||
writeSignalLog({
|
||||
signal_id: ackSignalId,
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
signal_type: 'GL-ACK',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: cmdSignal.related_dev,
|
||||
related_module: cmdSignal.related_module,
|
||||
summary: `ACK: ${signal.command} ${execResult.result}`,
|
||||
payload: ackPayload,
|
||||
result: execResult.result,
|
||||
ack_signal_id: null
|
||||
});
|
||||
pushToNotion({
|
||||
signal_id: ackSignalId,
|
||||
trace_id: signal.trace_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
signal_type: 'GL-ACK',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: cmdSignal.related_dev,
|
||||
related_module: cmdSignal.related_module,
|
||||
summary: `ACK: ${signal.command} ${execResult.result}`,
|
||||
payload: ackPayload
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`❌ 发送 ACK 邮件失败: ${err.message}`);
|
||||
errors++;
|
||||
}
|
||||
|
||||
// 标记邮件为已读
|
||||
await client.messageFlagsAdd(msg.seq, ['\\Seen']);
|
||||
|
||||
processed++;
|
||||
console.log(`✅ 信号处理完成: ${signal.trace_id} → ${execResult.result}`);
|
||||
if (execResult.detail) console.log(` 详情: ${execResult.detail}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.release();
|
||||
}
|
||||
} finally {
|
||||
await client.logout();
|
||||
}
|
||||
|
||||
console.log(`\n═══════════════════════════════════════════════`);
|
||||
console.log(`📡 ESP 处理完毕 · 处理: ${processed} · 错误: ${errors}`);
|
||||
console.log(`═══════════════════════════════════════════════`);
|
||||
|
||||
if (errors > 0) process.exit(1);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('❌ ESP 处理器致命错误:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
// scripts/psp-inspection.js
|
||||
// 铸渊 PSP 分身巡检 · 每日自动检查清单
|
||||
// CHK-G01 信号完整性 / CHK-G02 dev-nodes 一致性 / CHK-G03 notion-push 清理
|
||||
// CHK-G04 broadcasts 投递 / CHK-G05 CI 健康
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
// ── 目录配置 ───────────────────────────────────────────────────────────────
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const SIGNAL_LOG_DIR = path.join(ROOT, 'signal-log');
|
||||
const SIGNAL_INDEX = path.join(SIGNAL_LOG_DIR, 'index.json');
|
||||
const DEV_NODES_DIR = path.join(ROOT, 'dev-nodes');
|
||||
const NOTION_PENDING = path.join(ROOT, 'notion-push/pending');
|
||||
const BROADCASTS_DIR = path.join(ROOT, 'broadcasts-outbox');
|
||||
const MEMORY_PATH = path.join(ROOT, '.github/brain/memory.json');
|
||||
|
||||
const EMAIL_ADDRESS = process.env.EMAIL_ADDRESS;
|
||||
const EMAIL_PASSWORD = process.env.EMAIL_APP_PASSWORD;
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
|
||||
|
||||
const now = new Date();
|
||||
const today = now.toISOString().split('T')[0];
|
||||
const results = {};
|
||||
const issues = [];
|
||||
const autoFixed = [];
|
||||
const MS_PER_HOUR = 3600000;
|
||||
const PSP_TRACE_ID = `TRC-${today.replace(/-/g, '')}-PSP`;
|
||||
|
||||
// ── 工具函数 ───────────────────────────────────────────────────────────────
|
||||
function hoursAgo(isoStr) {
|
||||
if (!isoStr) return Infinity;
|
||||
return (Date.now() - new Date(isoStr).getTime()) / MS_PER_HOUR;
|
||||
}
|
||||
|
||||
function loadJson(filePath) {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
|
||||
}
|
||||
|
||||
function saveJson(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
// ── 信号 ID 生成 ───────────────────────────────────────────────────────────
|
||||
function generateSignalId() {
|
||||
const ymd = today.replace(/-/g, '');
|
||||
const index = loadJson(SIGNAL_INDEX) || { total_count: 0 };
|
||||
const seq = String(index.total_count + 1).padStart(3, '0');
|
||||
return `SIG-${ymd}-${seq}`;
|
||||
}
|
||||
|
||||
// ── 写信号日志 ─────────────────────────────────────────────────────────────
|
||||
function writeSignalLog(signal) {
|
||||
const dateStr = signal.timestamp.slice(0, 7);
|
||||
const dir = path.join(SIGNAL_LOG_DIR, dateStr);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, `${signal.signal_id}.json`), JSON.stringify(signal, null, 2));
|
||||
|
||||
const index = loadJson(SIGNAL_INDEX) || { last_updated: '', total_count: 0, signals: [] };
|
||||
index.total_count += 1;
|
||||
index.last_updated = now.toISOString();
|
||||
index.signals.unshift({
|
||||
signal_id: signal.signal_id,
|
||||
trace_id: signal.trace_id,
|
||||
type: signal.signal_type,
|
||||
timestamp: signal.timestamp,
|
||||
summary: signal.summary,
|
||||
related_dev: signal.related_dev || null,
|
||||
file: `${dateStr}/${signal.signal_id}.json`
|
||||
});
|
||||
saveJson(SIGNAL_INDEX, index);
|
||||
}
|
||||
|
||||
// ── 发送 GL-DATA 通知邮件 ──────────────────────────────────────────────────
|
||||
async function sendDataEmail(subject, payload) {
|
||||
if (!EMAIL_ADDRESS || !EMAIL_PASSWORD) {
|
||||
console.log('⚠️ EMAIL 未配置,跳过邮件发送');
|
||||
return;
|
||||
}
|
||||
const transport = nodemailer.createTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: { user: EMAIL_ADDRESS, pass: EMAIL_PASSWORD }
|
||||
});
|
||||
await transport.sendMail({
|
||||
from: EMAIL_ADDRESS,
|
||||
to: EMAIL_ADDRESS,
|
||||
subject: `[GL-DATA] ${subject}`,
|
||||
text: JSON.stringify(payload, null, 2)
|
||||
});
|
||||
console.log(`📧 [GL-DATA] 已发送: ${subject}`);
|
||||
}
|
||||
|
||||
// ── CHK-G01 信号收发完整性 ─────────────────────────────────────────────────
|
||||
function checkG01() {
|
||||
console.log('\n─── CHK-G01 信号收发完整性 ───');
|
||||
const index = loadJson(SIGNAL_INDEX);
|
||||
if (!index) {
|
||||
results.G01 = '⚠️';
|
||||
issues.push('CHK-G01: signal-log/index.json 不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const cutoff = Date.now() - 24 * MS_PER_HOUR;
|
||||
const recent = index.signals.filter(s => new Date(s.timestamp).getTime() > cutoff);
|
||||
const cmds = recent.filter(s => s.type === 'GL-CMD');
|
||||
const acks = new Set(recent.filter(s => s.type === 'GL-ACK').map(s => s.trace_id));
|
||||
const unacked = cmds.filter(s => !acks.has(s.trace_id));
|
||||
|
||||
if (unacked.length > 0) {
|
||||
results.G01 = '❌';
|
||||
unacked.forEach(s => issues.push(`CHK-G01: 未收到 ACK · trace_id=${s.trace_id} · ${s.summary}`));
|
||||
} else {
|
||||
results.G01 = '✅';
|
||||
console.log(` ✅ 最近24h CMD: ${cmds.length} · ACK 全部到位`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CHK-G02 dev-nodes 状态一致性 ──────────────────────────────────────────
|
||||
function checkG02() {
|
||||
console.log('\n─── CHK-G02 dev-nodes 状态一致性 ───');
|
||||
if (!fs.existsSync(DEV_NODES_DIR)) {
|
||||
results.G02 = '❌';
|
||||
issues.push('CHK-G02: dev-nodes/ 目录不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const devDirs = fs.readdirSync(DEV_NODES_DIR).filter(d => d.startsWith('DEV-'));
|
||||
let allOk = true;
|
||||
|
||||
for (const devId of devDirs) {
|
||||
const statusPath = path.join(DEV_NODES_DIR, devId, 'status.json');
|
||||
const configPath = path.join(DEV_NODES_DIR, devId, 'config.json');
|
||||
if (!fs.existsSync(statusPath) || !fs.existsSync(configPath)) {
|
||||
issues.push(`CHK-G02: ${devId} 缺少 status.json 或 config.json`);
|
||||
allOk = false;
|
||||
continue;
|
||||
}
|
||||
const status = loadJson(statusPath);
|
||||
if (!status) {
|
||||
issues.push(`CHK-G02: ${devId} status.json 解析失败`);
|
||||
allOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
results.G02 = allOk ? '✅' : '⚠️';
|
||||
if (allOk) console.log(` ✅ ${devDirs.length} 个开发者节点状态文件完整`);
|
||||
}
|
||||
|
||||
// ── CHK-G03 notion-push 清理 ───────────────────────────────────────────────
|
||||
async function checkG03() {
|
||||
console.log('\n─── CHK-G03 notion-push/pending 清理 ───');
|
||||
if (!fs.existsSync(NOTION_PENDING)) {
|
||||
results.G03 = '✅';
|
||||
console.log(' ✅ notion-push/pending 目录为空或不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(NOTION_PENDING).filter(f => f.endsWith('.json'));
|
||||
const stale = files.filter(f => {
|
||||
const stat = fs.statSync(path.join(NOTION_PENDING, f));
|
||||
return (Date.now() - stat.mtimeMs) > 24 * MS_PER_HOUR;
|
||||
});
|
||||
|
||||
if (stale.length > 0) {
|
||||
results.G03 = '⚠️';
|
||||
issues.push(`CHK-G03: ${stale.length} 个 notion-push/pending 文件超过24h未处理: ${stale.join(', ')}`);
|
||||
// 发通知
|
||||
try {
|
||||
await sendDataEmail('notion-push/pending 文件积压提醒', {
|
||||
type: 'GL-CMD',
|
||||
sender: '铸渊',
|
||||
command: 'alert_pending_files',
|
||||
payload: { stale_files: stale, count: stale.length }
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(` ⚠️ 发送通知失败: ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
results.G03 = '✅';
|
||||
console.log(` ✅ pending: ${files.length} 个文件,无超期`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CHK-G04 broadcasts 投递确认 ───────────────────────────────────────────
|
||||
function checkG04() {
|
||||
console.log('\n─── CHK-G04 broadcasts-outbox 投递确认 ───');
|
||||
if (!fs.existsSync(BROADCASTS_DIR)) {
|
||||
results.G04 = '⚠️';
|
||||
issues.push('CHK-G04: broadcasts-outbox/ 不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const devDirs = fs.readdirSync(BROADCASTS_DIR).filter(d => d.startsWith('DEV-'));
|
||||
let allSynced = true;
|
||||
|
||||
for (const devId of devDirs) {
|
||||
const srcDir = path.join(BROADCASTS_DIR, devId);
|
||||
const destDir = path.join(DEV_NODES_DIR, devId);
|
||||
if (!fs.existsSync(destDir)) continue;
|
||||
|
||||
const broadcasts = fs.readdirSync(srcDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of broadcasts) {
|
||||
const dest = path.join(destDir, file);
|
||||
if (!fs.existsSync(dest)) {
|
||||
// 自动复制
|
||||
fs.copyFileSync(path.join(srcDir, file), dest);
|
||||
autoFixed.push(`CHK-G04: 自动投递广播 ${devId}/${file}`);
|
||||
console.log(` 🔧 自动投递: ${devId}/${file}`);
|
||||
allSynced = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.G04 = '✅';
|
||||
if (allSynced) {
|
||||
console.log(' ✅ 所有广播已同步至 dev-nodes/');
|
||||
} else {
|
||||
console.log(` ✅ 已自动修复 ${autoFixed.length} 个广播投递`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── CHK-G05 CI 健康检查 ───────────────────────────────────────────────────
|
||||
async function checkG05() {
|
||||
console.log('\n─── CHK-G05 CI 健康检查 ───');
|
||||
if (!GITHUB_TOKEN) {
|
||||
results.G05 = '⚠️';
|
||||
console.log(' ⚠️ GITHUB_TOKEN 未配置,跳过 CI 检查');
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const [owner, repoName] = REPO.split('/');
|
||||
const options = {
|
||||
hostname: 'api.github.com',
|
||||
path: `/repos/${owner}/${repoName}/actions/runs?per_page=20`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'zhuyuan-psp-inspector'
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const body = JSON.parse(data);
|
||||
const runs = body.workflow_runs || [];
|
||||
const cutoff = Date.now() - 24 * MS_PER_HOUR;
|
||||
const recent = runs.filter(r => new Date(r.created_at).getTime() > cutoff);
|
||||
const failed = recent.filter(r => r.conclusion === 'failure');
|
||||
|
||||
if (failed.length > 0) {
|
||||
results.G05 = '❌';
|
||||
failed.forEach(r => {
|
||||
issues.push(`CHK-G05: CI 失败 · ${r.name} · ${r.html_url}`);
|
||||
});
|
||||
// 写入 signal-log
|
||||
const signalId = generateSignalId();
|
||||
writeSignalLog({
|
||||
signal_id: signalId,
|
||||
trace_id: PSP_TRACE_ID,
|
||||
timestamp: now.toISOString(),
|
||||
signal_type: 'GL-DATA',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: null,
|
||||
related_module: null,
|
||||
summary: `CHK-G05: ${failed.length} 个 CI 失败`,
|
||||
payload: { failed_runs: failed.map(r => ({ name: r.name, url: r.html_url })) },
|
||||
result: '待处理',
|
||||
ack_signal_id: null
|
||||
});
|
||||
} else {
|
||||
results.G05 = '✅';
|
||||
console.log(` ✅ 最近24h CI: ${recent.length} 次运行,全部通过`);
|
||||
}
|
||||
} catch (err) {
|
||||
results.G05 = '⚠️';
|
||||
console.log(` ⚠️ CI 数据解析失败: ${err.message}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
results.G05 = '⚠️';
|
||||
console.log(` ⚠️ GitHub API 请求失败: ${err.message}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// ── 更新 memory.json ───────────────────────────────────────────────────────
|
||||
function updateMemory(summary) {
|
||||
const memory = loadJson(MEMORY_PATH);
|
||||
if (!memory) return;
|
||||
|
||||
if (!memory.events) memory.events = [];
|
||||
memory.events.unshift({
|
||||
date: today,
|
||||
type: 'psp_inspection',
|
||||
description: summary,
|
||||
by: '铸渊PSP巡检'
|
||||
});
|
||||
if (memory.events.length > 50) memory.events = memory.events.slice(0, 50);
|
||||
memory.last_updated = now.toISOString();
|
||||
|
||||
saveJson(MEMORY_PATH, memory);
|
||||
}
|
||||
|
||||
// ── 写入巡检结果到 signal-log ──────────────────────────────────────────────
|
||||
function writeInspectionSignal(allPassed) {
|
||||
const signalId = generateSignalId();
|
||||
const summary = allPassed
|
||||
? '铸渊 PSP 巡检通过 · 全部检查项 ✅'
|
||||
: `铸渊 PSP 巡检完成 · 发现 ${issues.length} 个问题 · 自动修复 ${autoFixed.length} 项`;
|
||||
|
||||
writeSignalLog({
|
||||
signal_id: signalId,
|
||||
trace_id: PSP_TRACE_ID,
|
||||
timestamp: now.toISOString(),
|
||||
signal_type: 'GL-DATA',
|
||||
direction: 'GitHub→Notion',
|
||||
sender: '铸渊',
|
||||
receiver: '霜砚',
|
||||
related_dev: null,
|
||||
related_module: null,
|
||||
summary,
|
||||
payload: {
|
||||
check_results: results,
|
||||
issues,
|
||||
auto_fixed: autoFixed
|
||||
},
|
||||
result: allPassed ? '全通过' : '有问题',
|
||||
ack_signal_id: null
|
||||
});
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
// ── 主流程 ────────────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
console.log('═══════════════════════════════════════════════════');
|
||||
console.log('🔍 铸渊 PSP 分身巡检启动');
|
||||
console.log(` 日期: ${today}`);
|
||||
console.log('═══════════════════════════════════════════════════');
|
||||
|
||||
checkG01();
|
||||
checkG02();
|
||||
await checkG03();
|
||||
checkG04();
|
||||
await checkG05();
|
||||
|
||||
const allPassed = Object.values(results).every(v => v === '✅');
|
||||
const summary = writeInspectionSignal(allPassed);
|
||||
updateMemory(summary);
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════');
|
||||
console.log('📊 PSP 巡检报告:');
|
||||
Object.entries(results).forEach(([k, v]) => console.log(` ${v} CHK-${k}`));
|
||||
|
||||
if (autoFixed.length > 0) {
|
||||
console.log('\n🔧 自动修复:');
|
||||
autoFixed.forEach(f => console.log(` · ${f}`));
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
console.log('\n⚠️ 待处理问题:');
|
||||
issues.forEach(i => console.log(` · ${i}`));
|
||||
}
|
||||
|
||||
console.log(`\n${allPassed ? '✅ 铸渊 PSP 巡检全部通过' : '⚠️ 铸渊 PSP 巡检发现问题,已记录'}`);
|
||||
console.log('═══════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('❌ PSP 巡检致命错误:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -33,12 +33,15 @@ if (missing.length > 0) {
|
|||
}
|
||||
|
||||
// === ① bis · 目录结构巡检 ===
|
||||
const requiredDirs = ['syslog-inbox', 'syslog-processed', 'broadcasts-outbox', '.github/brain'];
|
||||
const requiredDirs = [
|
||||
'syslog-inbox', 'syslog-processed', 'broadcasts-outbox', '.github/brain',
|
||||
'signal-log', 'dev-nodes', 'notion-push/pending', 'notion-push/processed'
|
||||
];
|
||||
const missingDirs = requiredDirs.filter(d => !fs.existsSync(d));
|
||||
if (missingDirs.length > 0) {
|
||||
console.error('⚠️ 缺失目录:' + missingDirs.join(', '));
|
||||
} else {
|
||||
console.log('✅ 目录结构巡检:syslog-inbox / syslog-processed / broadcasts-outbox 全部就绪');
|
||||
console.log('✅ 目录结构巡检:syslog-inbox / syslog-processed / broadcasts-outbox / signal-log / dev-nodes / notion-push 全部就绪');
|
||||
}
|
||||
|
||||
// 统计 syslog-inbox 待处理条目
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"signal_id": "SIG-2026-0306-001",
|
||||
"trace_id": "TRC-2026-0306-001",
|
||||
"timestamp": "2026-03-06T23:00:00+08:00",
|
||||
"signal_type": "GL-CMD",
|
||||
"direction": "Notion→GitHub",
|
||||
"sender": "霜砚",
|
||||
"receiver": "铸渊",
|
||||
"related_dev": null,
|
||||
"related_module": null,
|
||||
"summary": "AGE OS 系统初始化 · 铸渊 GitHub 端激活",
|
||||
"payload": {
|
||||
"command": "system_init",
|
||||
"version": "1.0"
|
||||
},
|
||||
"result": "成功",
|
||||
"ack_signal_id": null
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"signal_id": "SIG-20260307-002",
|
||||
"trace_id": "TRC-20260307-PSP",
|
||||
"timestamp": "2026-03-07T04:45:55.670Z",
|
||||
"signal_type": "GL-DATA",
|
||||
"direction": "GitHub→Notion",
|
||||
"sender": "铸渊",
|
||||
"receiver": "霜砚",
|
||||
"related_dev": null,
|
||||
"related_module": null,
|
||||
"summary": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
|
||||
"payload": {
|
||||
"check_results": {
|
||||
"G01": "❌",
|
||||
"G02": "✅",
|
||||
"G03": "✅",
|
||||
"G04": "✅",
|
||||
"G05": "⚠️"
|
||||
},
|
||||
"issues": [
|
||||
"CHK-G01: 未收到 ACK · trace_id=TRC-2026-0306-001 · AGE OS 系统初始化 · 铸渊 GitHub 端激活"
|
||||
],
|
||||
"auto_fixed": []
|
||||
},
|
||||
"result": "有问题",
|
||||
"ack_signal_id": null
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"description": "铸渊信号日志目录索引 · AGE OS ESP 邮件信号协议",
|
||||
"last_updated": "2026-03-07T04:45:55.670Z",
|
||||
"total_count": 2,
|
||||
"signals": [
|
||||
{
|
||||
"signal_id": "SIG-20260307-002",
|
||||
"trace_id": "TRC-20260307-PSP",
|
||||
"type": "GL-DATA",
|
||||
"timestamp": "2026-03-07T04:45:55.670Z",
|
||||
"summary": "铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项",
|
||||
"related_dev": null,
|
||||
"file": "2026-03/SIG-20260307-002.json"
|
||||
},
|
||||
{
|
||||
"signal_id": "SIG-2026-0306-001",
|
||||
"trace_id": "TRC-2026-0306-001",
|
||||
"type": "GL-CMD",
|
||||
"timestamp": "2026-03-06T23:00:00+08:00",
|
||||
"summary": "AGE OS 系统初始化 · 铸渊 GitHub 端激活",
|
||||
"related_dev": null,
|
||||
"file": "2026-03/SIG-2026-0306-001.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue