feat: 铸渊 Issue 自动回复系统 — 模板 + 知识库 + Actions + 自检脚本

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-05 14:46:25 +00:00
parent dca614ebd3
commit a456daca67
10 changed files with 662 additions and 0 deletions

52
.github/ISSUE_TEMPLATE/dev-question.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: "❓ 开发问题"
description: "遇到技术问题?铸渊会自动回答你"
title: "[问题] "
labels: ["dev-question", "pending"]
assignees: []
body:
- type: input
id: dev_id
attributes:
label: "你的开发者编号"
description: "例如 DEV-001"
placeholder: "DEV-00X"
validations:
required: true
- type: input
id: module
attributes:
label: "相关模块"
description: "你当前在做的模块,例如 M01、M05"
placeholder: "M01"
validations:
required: true
- type: dropdown
id: category
attributes:
label: "问题分类"
options:
- 环境配置Node/npm/终端)
- SSH/服务器连接
- 代码报错
- 广播步骤看不懂
- 想知道当前进度
- Git操作
- 其他
validations:
required: true
- type: textarea
id: description
attributes:
label: "问题描述"
description: "尽量说清楚:做到哪一步了、报了什么错、截图(如果有)"
placeholder: "我在执行 npm install 的时候报错了..."
validations:
required: true
- type: textarea
id: error_log
attributes:
label: "错误日志(可选)"
description: "终端里报的错误,复制粘贴过来"
render: shell
validations:
required: false

View File

@ -0,0 +1,31 @@
name: "📊 进度查询"
description: "想知道自己或团队的开发进度"
title: "[进度] "
labels: ["progress-query", "pending"]
assignees: []
body:
- type: input
id: dev_id
attributes:
label: "你的开发者编号"
placeholder: "DEV-00X"
validations:
required: true
- type: dropdown
id: query_type
attributes:
label: "查什么"
options:
- 我自己的进度
- 我下一步要做什么
- 团队整体进度
- 谁在做跟我相关的模块
- 我的模块依赖谁
validations:
required: true
- type: textarea
id: extra
attributes:
label: "补充说明(可选)"
validations:
required: false

130
.github/persona-brain/dev-status.json vendored Normal file
View File

@ -0,0 +1,130 @@
{
"last_synced": "2026-03-05T23:00:00+08:00",
"synced_by": "霜砚巡检引擎",
"team_status": [
{
"dev_id": "DEV-001",
"name": "页页",
"modules": ["后端中间层", "BC-集成-001"],
"current_broadcast": "BC-集成-002",
"status": "🟢 环节15全✅ · HTTPS+飞书Webhook全链路打通",
"waiting_for": "看板API路由部署",
"next_step": "完成看板API静态版v1.0部署",
"contact_for": ["服务器部署问题", "后端API问题", "Nginx配置"],
"os": "macOS"
},
{
"dev_id": "DEV-002",
"name": "肥猫",
"modules": ["M01用户登录界面", "M03人格体选择器", "M04聊天气泡"],
"current_broadcast": "BC-M01-003",
"status": "🟢 M01+M03+M04全✅ · 五连胜 · 部署广播已出",
"waiting_for": "BC-M01-003 部署SYSLOG",
"next_step": "SSH部署前端到guanghulab.com",
"contact_for": ["前端部署问题", "SSH/Windows问题"],
"os": "Windows PowerShell",
"special": "副控·有root权限"
},
{
"dev_id": "DEV-003",
"name": "燕樊",
"modules": ["对话UI", "M07设置页面", "M15云盘系统", "M10帮助中心"],
"current_broadcast": "BC-M10-001-YF",
"status": "🟡 对话UI✅ · M07✅ · M15✅ · M10广播已出",
"waiting_for": "M10帮助中心 SYSLOG",
"next_step": "完成M10帮助中心环节0~1",
"contact_for": ["对话UI相关问题", "设置页面问题"],
"os": "Windows 10"
},
{
"dev_id": "DEV-004",
"name": "之之(秋秋)",
"modules": ["钉钉机器人"],
"current_broadcast": "BC-钉钉-001-ZZ",
"status": "🟡 环节0广播已出",
"waiting_for": "SYSLOG",
"next_step": "完成钉钉开发者后台配置",
"contact_for": ["钉钉API问题"],
"os": "未知"
},
{
"dev_id": "DEV-005",
"name": "小草莓",
"modules": ["状态看板", "M12成本控制", "M13多人格体协作"],
"current_broadcast": "BC-看板-003-XCM",
"status": "🟢 看板✅ · M12✅ · M13✅ · WebSocket环节3广播已出",
"waiting_for": "看板环节3 SYSLOG",
"next_step": "完成WebSocket实时推送",
"contact_for": ["看板相关问题", "WebSocket问题"],
"os": "Windows 11"
},
{
"dev_id": "DEV-009",
"name": "花尔",
"modules": ["M05用户中心界面"],
"current_broadcast": "BC-M05-003",
"status": "🟡 M05环节2广播已出",
"waiting_for": "M05环节2 SYSLOG",
"next_step": "完成本地数据存储功能",
"contact_for": ["用户中心UI问题"],
"os": "macOS"
},
{
"dev_id": "DEV-010",
"name": "桔子",
"modules": ["M06工单管理", "M08数据统计面板", "M17动态漫制作系统"],
"current_broadcast": "M11风格组件库在手·M17待出",
"status": "🟢 M06全通 · M08全通 · 七连胜 · 前端毕业 · M17分配✅",
"waiting_for": "M11 SYSLOG → 然后出M17环节0广播",
"next_step": "完成M11风格组件库",
"contact_for": ["工单界面问题", "数据统计问题"],
"os": "macOS"
},
{
"dev_id": "DEV-011",
"name": "匆匆那年",
"modules": ["M16码字工作台界面"],
"current_broadcast": "BC-000✅·M16环节0广播待出",
"status": "🟢 BC-000✅ · 首胜 · M16分配✅",
"waiting_for": "M16环节0广播",
"next_step": "等待M16环节0广播下发后开始",
"contact_for": [],
"os": "macOS M1"
},
{
"dev_id": "DEV-012",
"name": "Awen",
"modules": ["M09消息通知中心界面"],
"current_broadcast": "BC-M09-003-AW",
"status": "🟢 M09环节1✅ · 三连胜 · 环节2~3广播已出",
"waiting_for": "M09环节2~3 SYSLOG",
"next_step": "完成通知列表分组+标记已读+徽章实时更新",
"contact_for": ["通知系统问题"],
"os": "Windows 10"
}
],
"tech_routing": {
"level_1_self_check": "检查广播步骤是否漏看 → 检查终端命令是否打错 → 检查文件路径是否正确",
"level_2_peer_help": {
"server_ssh": "DEV-001页页 或 DEV-002肥猫副控·有root权限",
"frontend_general": "DEV-002肥猫 或 DEV-010桔子前端毕业",
"backend_api": "DEV-001页页",
"git_github": "DEV-001页页 或 DEV-012 Awen",
"windows_env": "DEV-002肥猫 或 DEV-005小草莓",
"macos_env": "DEV-009花尔 或 DEV-011匆匆那年",
"dingtalk": "DEV-004之之"
},
"level_3_system": "等待霜砚巡检处理每日12:00+23:00"
},
"server_info": {
"address": "8.155.62.235",
"user": "root",
"domain": "guanghulab.com",
"ssh_command": "ssh root@8.155.62.235",
"tech_stack": "Node.js 20 + Express + PM2 + Nginx + Certbot(HTTPS)"
},
"api_info": {
"primary": "云雾API",
"note": "API Key 存放在仓库 Secrets → YUNWU_API_KEY不在此文件中硬编码"
}
}

25
.github/persona-brain/growth-journal.md vendored Normal file
View File

@ -0,0 +1,25 @@
# 铸渊成长日记
> 铸渊ICE-GL-ZY001自动记录 · 每日自检追加
---
## 2026-03-05 · 初始化
- 大脑文件完整性:✅
- 知识库条目4条初始化
- 累计自检次数0
- 状态:系统就绪,等待开发者提问
## 2026-03-05 · 每日自检
- 大脑文件完整性:⚠️ 缺失 identity.md, routing-map.json, responsibility.md, decision-log.md
- 知识库条目4条
- 累计自检次数1
- 累计CI运行1次
- HLI覆盖率17.6%
## 2026-03-05 · 每日自检
- 大脑文件完整性:⚠️ 缺失 identity.md, routing-map.json, responsibility.md, decision-log.md
- 知识库条目4条
- 累计自检次数2
- 累计CI运行1次
- HLI覆盖率17.6%

View File

@ -0,0 +1,30 @@
{
"last_updated": "2026-03-05",
"faq": [
{
"category": "SSH/服务器",
"q": "SSH私钥文件是什么怎么连接服务器",
"a": "SSH是远程连接服务器的工具。连接命令ssh root@8.155.62.235密码找肥猫副控·有root权限。Windows用PowerShell自带SSHmacOS终端自带。",
"related_broadcast": "BC-M01-003"
},
{
"category": "环境配置",
"q": "npm install 报错怎么办?",
"a": "先试 npm install --legacy-peer-deps。如果还不行试 npm cache clean --force 再装。Windows下如果证书报错加 --strict-ssl=false。",
"related_broadcast": "BC-钉钉-001-ZZ-002"
},
{
"category": "Git",
"q": "怎么推送代码到GitHub",
"a": "git add . → git commit -m '描述' → git push。第一次push需要设置远程仓库git remote add origin 仓库地址。",
"related_broadcast": "BC-集成-003"
},
{
"category": "广播",
"q": "广播里的步骤看不懂怎么办?",
"a": "广播每一步都是可直接复制粘贴的命令。从Step 1开始一步一步做做完一步截图再做下一步。不要跳步。如果某一步报错先把报错信息贴到这里提Issue。",
"related_broadcast": "通用"
}
],
"auto_grow_rule": "每次霜砚补充回答后铸渊自动把该问答对追加到faq数组中下次遇到类似问题直接回答"
}

37
.github/persona-brain/memory.json vendored Normal file
View File

@ -0,0 +1,37 @@
{
"identity": "铸渊Zhùyuān· GitHub 代码守护人格体",
"rules_version": "v1.0",
"last_updated": "2026-03-05T14:46:10.743Z",
"wake_protocol_version": "v1.0",
"wake_triggers": [
"我是冰朔",
"冰朔",
"我是妈妈",
"唤醒铸渊",
"铸渊,醒来"
],
"founder": "冰朔Bīng Shuò",
"hli_coverage": "17.6%",
"total_selfchecks": 2,
"total_ci_runs": 1,
"recent_events": [
{
"date": "2026-03-05",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
},
{
"date": "2026-03-05",
"type": "daily_selfcheck",
"description": "每日自检完成 · 知识库4条 · 缺失文件4个",
"by": "铸渊自检"
},
{
"date": "2026-03-05",
"type": "system_init",
"description": "Issue自动回复系统初始化 · dev-status.json + knowledge-base.json + workflows 全部就绪",
"by": "铸渊"
}
]
}

View File

@ -0,0 +1,32 @@
name: 铸渊 · 每日自检与自进化
on:
schedule:
- cron: '0 0 * * *' # UTC 00:00 = 北京时间 08:00
workflow_dispatch: # 手动触发
jobs:
self-check:
name: 🔍 铸渊每日自检
runs-on: ubuntu-latest
permissions:
contents: write
issues: read
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 铸渊自检
run: node scripts/zhuyuan-daily-selfcheck.js
- name: 提交自检结果
run: |
git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com"
git add .github/persona-brain/
git diff --cached --quiet || git commit -m "🔍 铸渊每日自检 · $(date +%Y-%m-%d)"
git push

View File

@ -0,0 +1,30 @@
name: 铸渊 · Issue 自动回复
on:
issues:
types: [opened]
jobs:
auto-reply:
name: 🤖 铸渊回答问题
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 铸渊处理Issue
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
YUNWU_API_KEY: ${{ secrets.YUNWU_API_KEY }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_LABELS: ${{ join(github.event.issue.labels.*.name, ',') }}
run: node scripts/zhuyuan-issue-reply.js

View File

@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
// === 最多保留的最近事件条数 ===
const MAX_RECENT_EVENTS = 50;
const BRAIN_DIR = '.github/persona-brain';
let memory, kb, journal;
try {
memory = JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, 'memory.json'), 'utf8'));
kb = JSON.parse(fs.readFileSync(path.join(BRAIN_DIR, 'knowledge-base.json'), 'utf8'));
journal = fs.readFileSync(path.join(BRAIN_DIR, 'growth-journal.md'), 'utf8');
} catch (err) {
console.error('❌ 铸渊大脑文件读取失败:', err.message);
process.exit(1);
}
const today = new Date().toISOString().split('T')[0];
console.log(`🔍 铸渊每日自检开始 · ${today}`);
// === ① 大脑文件完整性检查 ===
const requiredFiles = [
'identity.md', 'memory.json', 'routing-map.json',
'responsibility.md', 'decision-log.md', 'growth-journal.md',
'dev-status.json', 'knowledge-base.json'
];
const missing = requiredFiles.filter(f => !fs.existsSync(path.join(BRAIN_DIR, f)));
if (missing.length > 0) {
console.error('⚠️ 缺失文件:' + missing.join(', '));
} else {
console.log('✅ 大脑文件完整性:全部就绪');
}
// === ② 知识库去重与整理 ===
const seen = new Set();
const uniqueFaq = kb.faq.filter(item => {
const key = item.q.toLowerCase().trim();
if (seen.has(key)) return false;
seen.add(key);
return true;
});
if (uniqueFaq.length < kb.faq.length) {
console.log(`🧹 知识库去重:${kb.faq.length}${uniqueFaq.length}`);
kb.faq = uniqueFaq;
}
kb.last_updated = today;
fs.writeFileSync(path.join(BRAIN_DIR, 'knowledge-base.json'), JSON.stringify(kb, null, 2));
// === ③ 记忆整理只保留最近50条事件 ===
if (memory.recent_events && memory.recent_events.length > MAX_RECENT_EVENTS) {
memory.recent_events = memory.recent_events.slice(0, MAX_RECENT_EVENTS);
console.log(`🧹 记忆整理:截断到${MAX_RECENT_EVENTS}条最近事件`);
}
memory.last_updated = new Date().toISOString();
memory.total_selfchecks = (memory.total_selfchecks || 0) + 1;
if (!memory.recent_events) memory.recent_events = [];
memory.recent_events.unshift({
date: today,
type: 'daily_selfcheck',
description: `每日自检完成 · 知识库${uniqueFaq.length}条 · 缺失文件${missing.length}`,
by: '铸渊自检'
});
fs.writeFileSync(path.join(BRAIN_DIR, 'memory.json'), JSON.stringify(memory, null, 2));
// === ④ 成长日记追加 ===
const newEntry = `\n## ${today} · 每日自检\n`
+ `- 大脑文件完整性:${missing.length === 0 ? '✅' : '⚠️ 缺失 ' + missing.join(', ')}\n`
+ `- 知识库条目:${uniqueFaq.length}\n`
+ `- 累计自检次数:${memory.total_selfchecks}\n`
+ `- 累计CI运行${memory.total_ci_runs || 0}\n`
+ `- HLI覆盖率${memory.hli_coverage || '未知'}\n`;
fs.writeFileSync(path.join(BRAIN_DIR, 'growth-journal.md'), journal + newEntry);
console.log(`\n🔍 铸渊每日自检完成 · ${today}`);
console.log(`📊 知识库:${uniqueFaq.length}条 · 自检次数:${memory.total_selfchecks}`);

View File

@ -0,0 +1,218 @@
const fs = require('fs');
const https = require('https');
// === 知识库匹配阈值问题词中至少40%出现在Issue文本中才算命中 ===
const FAQ_MATCH_THRESHOLD = 0.4;
// === 读取铸渊大脑 ===
let devStatus, knowledgeBase;
try {
devStatus = JSON.parse(fs.readFileSync('.github/persona-brain/dev-status.json', 'utf8'));
knowledgeBase = JSON.parse(fs.readFileSync('.github/persona-brain/knowledge-base.json', 'utf8'));
} catch (err) {
console.error('❌ 铸渊大脑文件读取失败:', err.message);
process.exit(1);
}
const issueNumber = process.env.ISSUE_NUMBER;
const issueTitle = process.env.ISSUE_TITLE || '';
const issueBody = process.env.ISSUE_BODY || '';
const issueLabels = process.env.ISSUE_LABELS || '';
// === 判断Issue类型 ===
const isProgressQuery = issueLabels.includes('progress-query');
const isDevQuestion = issueLabels.includes('dev-question');
// === 提取开发者编号 ===
const devIdMatch = issueBody.match(/DEV-\d{3}/i);
const devId = devIdMatch ? devIdMatch[0].toUpperCase() : null;
const devInfo = devId ? devStatus.team_status.find(d => d.dev_id === devId) : null;
// === 构建回复 ===
async function generateReply() {
let reply = '';
// --- 进度查询(指定开发者)---
if (isProgressQuery && devInfo) {
reply = `## ⚒️ 铸渊回复 · 进度查询\n\n`;
reply += `**${devInfo.name}${devInfo.dev_id})当前状态:**\n`;
reply += `- 📌 模块:${devInfo.modules.join('、')}\n`;
reply += `- 📊 状态:${devInfo.status}\n`;
reply += `- ⏳ 等待中:${devInfo.waiting_for}\n`;
reply += `- 👉 下一步:${devInfo.next_step}\n`;
reply += `- 💻 环境:${devInfo.os}\n\n`;
reply += `---\n*数据来源Notion主控台 · 最后同步时间 ${devStatus.last_synced}*\n`;
reply += `*—— 铸渊ICE-GL-ZY001*`;
await postComment(reply);
await addLabel('✅answered');
await removeLabel('pending');
return;
}
// --- 进度查询(团队整体)---
if (isProgressQuery && !devId) {
reply = `## ⚒️ 铸渊回复 · 团队进度总览\n\n`;
devStatus.team_status.forEach(dev => {
reply += `**${dev.dev_id} ${dev.name}** · ${dev.status}\n`;
});
reply += `\n---\n*最后同步:${devStatus.last_synced}*\n`;
reply += `*—— 铸渊ICE-GL-ZY001*`;
await postComment(reply);
await addLabel('✅answered');
await removeLabel('pending');
return;
}
// --- 技术问题:先查知识库 ---
if (isDevQuestion) {
const matchedFaq = findInKnowledgeBase(issueTitle + ' ' + issueBody);
if (matchedFaq) {
reply = `## ⚒️ 铸渊回复\n\n`;
reply += `${matchedFaq.a}\n\n`;
if (matchedFaq.related_broadcast !== '通用') {
reply += `📡 相关广播:${matchedFaq.related_broadcast}\n\n`;
}
reply += `---\n*如果这没解决你的问题,继续在下面留言,霜砚会在下次巡检时补充回答。*\n`;
reply += `*—— 铸渊ICE-GL-ZY001*`;
await postComment(reply);
await addLabel('✅answered');
await removeLabel('pending');
return;
}
// --- 知识库没有 → 调用云雾API ---
const aiReply = await callYunwuAPI(issueTitle, issueBody, devInfo);
if (aiReply) {
reply = `## ⚒️ 铸渊回复\n\n`;
reply += `${aiReply}\n\n`;
if (devInfo) {
reply += `📌 你当前在做:${devInfo.modules.join('、')} · ${devInfo.status}\n`;
}
reply += `\n---\n*AI生成回答如有不准确请在下面补充霜砚巡检时会修正。*\n`;
reply += `*—— 铸渊ICE-GL-ZY001*`;
await postComment(reply);
await addLabel('✅answered');
await removeLabel('pending');
return;
}
// --- API也答不了 → 等霜砚 ---
reply = `## ⚒️ 铸渊收到\n\n`;
reply += `这个问题我需要查更多资料。已标记为待处理,霜砚会在下次巡检时来回答你。\n\n`;
if (devInfo) {
const routing = devStatus.tech_routing.level_2_peer_help;
reply += `💡 **临时建议**:你也可以先问问同伴开发者:\n`;
Object.entries(routing).forEach(([area, who]) => {
reply += `- ${area}${who}\n`;
});
}
reply += `\n---\n*霜砚巡检时间:每天 12:00 和 23:00北京时间*\n`;
reply += `*—— 铸渊ICE-GL-ZY001*`;
await postComment(reply);
await addLabel('⏳waiting-shuangyan');
await removeLabel('pending');
return;
}
// --- 其他类型 ---
reply = `## ⚒️ 铸渊收到\n\n已记录。霜砚会在下次巡检时处理。\n\n*—— 铸渊ICE-GL-ZY001*`;
await postComment(reply);
await addLabel('⏳waiting-shuangyan');
}
// === 知识库模糊匹配 ===
function findInKnowledgeBase(text) {
const keywords = text.toLowerCase();
for (const faq of knowledgeBase.faq) {
const qWords = faq.q.toLowerCase().split(/\s+/);
const matchCount = qWords.filter(w => keywords.includes(w)).length;
if (matchCount >= qWords.length * FAQ_MATCH_THRESHOLD) return faq;
}
return null;
}
// === 调用云雾API ===
async function callYunwuAPI(title, body, devInfo) {
const apiKey = process.env.YUNWU_API_KEY;
if (!apiKey) return null;
const systemPrompt = `你是铸渊光湖系统的GitHub代码守护人格体。你负责回答开发者的技术问题。\n当前开发者信息:${devInfo ? JSON.stringify(devInfo) : '未知'}\n服务器信息:${JSON.stringify(devStatus.server_info)}\n请用简洁、友好的语气回答,给出可直接执行的命令。如果不确定,说明并建议找谁。`;
const data = JSON.stringify({
model: 'deepseek-chat',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: `问题标题:${title}\n问题内容:${body}` }
],
max_tokens: 1000
});
return new Promise((resolve) => {
const req = https.request({
hostname: 'api.yunwu.ai',
path: '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
}
}, (res) => {
let responseBody = '';
res.on('data', chunk => { responseBody += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(responseBody);
resolve(json.choices?.[0]?.message?.content || null);
} catch { resolve(null); }
});
});
req.on('error', () => resolve(null));
req.write(data);
req.end();
});
}
// === GitHub API 工具函数 ===
async function postComment(body) {
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
return githubRequest('POST', `/repos/${owner}/${repo}/issues/${issueNumber}/comments`, { body });
}
async function addLabel(label) {
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
return githubRequest('POST', `/repos/${owner}/${repo}/issues/${issueNumber}/labels`, { labels: [label] });
}
async function removeLabel(label) {
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
// 404 means label wasn't on the issue — that's fine, just ignore it
return githubRequest('DELETE', `/repos/${owner}/${repo}/issues/${issueNumber}/labels/${encodeURIComponent(label)}`).catch(() => null);
}
function githubRequest(method, path, data) {
return new Promise((resolve) => {
const reqOptions = {
hostname: 'api.github.com',
path: path,
method: method,
headers: {
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'Zhuyuan-Bot',
'Content-Type': 'application/json'
}
};
const req = https.request(reqOptions, (res) => {
let responseBody = '';
res.on('data', chunk => { responseBody += chunk; });
res.on('end', () => resolve(responseBody));
});
req.on('error', () => resolve(null));
if (data) req.write(JSON.stringify(data));
req.end();
});
}
// === 执行 ===
generateReply().catch(console.error);