Merge pull request #49 from qinfendebingshuo/copilot/fix-model-interaction-issues
fix: Persona Studio API Key chat CORS failure, bulletin push race condition, add daily inspection agent
This commit is contained in:
commit
f951ef1876
|
|
@ -82,6 +82,17 @@ jobs:
|
||||||
echo "📢 公告区无变化,跳过提交"
|
echo "📢 公告区无变化,跳过提交"
|
||||||
else
|
else
|
||||||
git commit -m "📢 自动更新系统公告区 [skip ci]"
|
git commit -m "📢 自动更新系统公告区 [skip ci]"
|
||||||
git push
|
# 带重试的推送(避免并发推送导致 rejected)
|
||||||
echo "✅ 公告区已更新并推送"
|
for i in 1 2 3; do
|
||||||
|
if git push; then
|
||||||
|
echo "✅ 公告区已更新并推送"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "⚠️ 推送失败(第 $i 次),拉取最新代码后重试..."
|
||||||
|
git pull --rebase origin main
|
||||||
|
if [ $i -eq 3 ]; then
|
||||||
|
echo "❌ 推送失败 3 次,放弃"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
# 铸渊 · 每日巡检 Agent
|
||||||
|
#
|
||||||
|
# 每天定时巡检仓库健康状况,自动检查今天遗漏的任务,
|
||||||
|
# 发现问题后自动触发对应的修复工作流。
|
||||||
|
#
|
||||||
|
# 触发条件:
|
||||||
|
# 1. 每日定时(北京时间 22:00 = UTC 14:00)
|
||||||
|
# 2. 手动触发
|
||||||
|
#
|
||||||
|
# 巡检项:
|
||||||
|
# CHK-1: 公告栏是否已更新
|
||||||
|
# CHK-2: 每日自检是否已执行
|
||||||
|
# CHK-3: 大脑文件完整性
|
||||||
|
# CHK-4: CI 最近状态
|
||||||
|
# CHK-5: 关键模块 README
|
||||||
|
# CHK-6: 公告栏工作流健康
|
||||||
|
|
||||||
|
name: 🤖 铸渊巡检 Agent · 每日自动巡检与修复
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# 北京时间 22:00 (UTC 14:00) — 每天收工前巡检
|
||||||
|
- cron: '0 14 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: zhuyuan-daily-agent
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
inspect:
|
||||||
|
name: 🔍 铸渊巡检 Agent
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
has_issues: ${{ steps.agent.outputs.has_issues }}
|
||||||
|
need_bulletin_update: ${{ steps.agent.outputs.need_bulletin_update }}
|
||||||
|
need_selfcheck: ${{ steps.agent.outputs.need_selfcheck }}
|
||||||
|
summary: ${{ steps.agent.outputs.summary }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 检出代码
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 50
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: 🟢 设置 Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: 🔍 执行巡检
|
||||||
|
id: agent
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
run: node scripts/zhuyuan-daily-agent.js
|
||||||
|
|
||||||
|
- name: 📝 提交巡检结果
|
||||||
|
run: |
|
||||||
|
git config user.name "铸渊 (ZhùYuān)"
|
||||||
|
git config user.email "zhuyuan@guanghulab.com"
|
||||||
|
git add .github/brain/memory.json
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "📋 无变化,跳过提交"
|
||||||
|
else
|
||||||
|
git commit -m "🤖 铸渊巡检Agent · $(date +%Y-%m-%d) [skip ci]"
|
||||||
|
for i in 1 2 3; do
|
||||||
|
if git push; then
|
||||||
|
echo "✅ 巡检结果已推送"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "⚠️ 推送失败(第 $i 次),拉取后重试..."
|
||||||
|
git pull --rebase origin main
|
||||||
|
if [ $i -eq 3 ]; then
|
||||||
|
echo "⚠️ 推送失败 3 次,跳过"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 自动修复:触发公告栏更新 ──
|
||||||
|
fix-bulletin:
|
||||||
|
name: 🔧 自动修复 · 公告栏更新
|
||||||
|
needs: inspect
|
||||||
|
if: needs.inspect.outputs.need_bulletin_update == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 🔄 触发公告栏更新工作流
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
workflow_id: 'update-readme-bulletin.yml',
|
||||||
|
ref: 'main',
|
||||||
|
});
|
||||||
|
console.log('✅ 已触发公告栏更新工作流');
|
||||||
|
|
||||||
|
# ── 自动修复:触发每日自检 ──
|
||||||
|
fix-selfcheck:
|
||||||
|
name: 🔧 自动修复 · 每日自检
|
||||||
|
needs: inspect
|
||||||
|
if: needs.inspect.outputs.need_selfcheck == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 🔄 触发每日自检工作流
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
workflow_id: 'zhuyuan-daily-selfcheck.yml',
|
||||||
|
ref: 'main',
|
||||||
|
});
|
||||||
|
console.log('✅ 已触发每日自检工作流');
|
||||||
|
|
||||||
|
# ── 巡检报告 ──
|
||||||
|
report:
|
||||||
|
name: 📊 巡检报告
|
||||||
|
needs: [inspect]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 📊 输出巡检摘要
|
||||||
|
run: |
|
||||||
|
echo "═══════════════════════════════════════════"
|
||||||
|
echo "🤖 铸渊巡检 Agent · 每日报告"
|
||||||
|
echo "═══════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
echo "📋 巡检结果: ${{ needs.inspect.outputs.summary }}"
|
||||||
|
echo "🔧 公告栏修复: ${{ needs.fix-bulletin.result || '未触发' }}"
|
||||||
|
echo "🔧 自检修复: ${{ needs.fix-selfcheck.result || '未触发' }}"
|
||||||
|
echo ""
|
||||||
|
echo "✅ 巡检 Agent 流程结束"
|
||||||
|
|
@ -140,87 +140,46 @@ async function sendMessage() {
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- API Key 流式响应(浏览器直连) ---- */
|
/* ---- API Key 对话(通过后端代理,避免 CORS 问题) ---- */
|
||||||
async function streamApiKeyReply(text) {
|
async function streamApiKeyReply(text) {
|
||||||
var apiMessages = conversationHistory.slice(-20).map(function (msg) {
|
var apiMessages = conversationHistory.slice(-20).map(function (msg) {
|
||||||
return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content };
|
return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content };
|
||||||
});
|
});
|
||||||
|
|
||||||
var chatUrl = USER_API_BASE + '/chat/completions';
|
|
||||||
var reqBody = {
|
|
||||||
model: SELECTED_MODEL,
|
|
||||||
messages: apiMessages,
|
|
||||||
stream: true,
|
|
||||||
max_tokens: 2000,
|
|
||||||
temperature: 0.8
|
|
||||||
};
|
|
||||||
|
|
||||||
var res = await fetch(chatUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + USER_API_KEY
|
|
||||||
},
|
|
||||||
body: JSON.stringify(reqBody)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
var errText = '请求失败 (HTTP ' + res.status + ')';
|
|
||||||
try {
|
|
||||||
var errData = await res.json();
|
|
||||||
errText = errData.error?.message || errText;
|
|
||||||
} catch (_e) { /* ignore parse error */ }
|
|
||||||
appendMessage('system', '⚠️ ' + errText);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 流式读取响应
|
|
||||||
var streamEl = appendStreamMessage();
|
var streamEl = appendStreamMessage();
|
||||||
var full = '';
|
|
||||||
var reader = res.body.getReader();
|
|
||||||
var decoder = new TextDecoder();
|
|
||||||
var buf = '';
|
|
||||||
|
|
||||||
while (true) {
|
try {
|
||||||
var chunk = await reader.read();
|
var res = await fetch(API_BASE + '/api/ps/apikey/chat', {
|
||||||
if (chunk.done) break;
|
method: 'POST',
|
||||||
buf += decoder.decode(chunk.value, { stream: true });
|
headers: { 'Content-Type': 'application/json' },
|
||||||
var lines = buf.split('\n');
|
body: JSON.stringify({
|
||||||
buf = lines.pop();
|
api_base: USER_API_BASE,
|
||||||
|
api_key: USER_API_KEY,
|
||||||
|
model: SELECTED_MODEL,
|
||||||
|
messages: apiMessages
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
if (!res.ok) {
|
||||||
var line = lines[i];
|
var errText = '请求失败 (HTTP ' + res.status + ')';
|
||||||
if (!line.startsWith('data: ')) continue;
|
|
||||||
var d = line.slice(6);
|
|
||||||
if (d === '[DONE]') continue;
|
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(d);
|
var errData = await res.json();
|
||||||
var delta = parsed.choices && parsed.choices[0]
|
errText = errData.message || errText;
|
||||||
&& parsed.choices[0].delta
|
|
||||||
&& parsed.choices[0].delta.content;
|
|
||||||
if (delta) {
|
|
||||||
full += delta;
|
|
||||||
streamEl.textContent = full;
|
|
||||||
}
|
|
||||||
} catch (_e) { /* ignore parse error */ }
|
} catch (_e) { /* ignore parse error */ }
|
||||||
|
streamEl.textContent = '⚠️ ' + errText;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有流式内容,尝试非流式解析
|
var data = await res.json();
|
||||||
if (!full && buf) {
|
|
||||||
try {
|
|
||||||
var jsonRes = JSON.parse(buf);
|
|
||||||
if (jsonRes.choices && jsonRes.choices[0] && jsonRes.choices[0].message) {
|
|
||||||
full = jsonRes.choices[0].message.content;
|
|
||||||
streamEl.textContent = full;
|
|
||||||
}
|
|
||||||
} catch (_e) { /* ignore */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (full) {
|
if (data.reply) {
|
||||||
conversationHistory.push({ role: 'assistant', content: full });
|
streamEl.textContent = data.reply;
|
||||||
} else {
|
conversationHistory.push({ role: 'assistant', content: data.reply });
|
||||||
streamEl.textContent = '(未收到有效回复)';
|
} else {
|
||||||
|
streamEl.textContent = '(未收到有效回复)';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
streamEl.textContent = '⚠️ ' + (err.message || '请求失败,请检查网络连接');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,336 @@
|
||||||
|
/**
|
||||||
|
* 铸渊 · 每日巡检 Agent
|
||||||
|
*
|
||||||
|
* 目的:每天自动巡检仓库健康状况,检查当天遗漏的任务,
|
||||||
|
* 生成巡检报告,并自动触发修复流程。
|
||||||
|
*
|
||||||
|
* 检查项:
|
||||||
|
* 1. 公告栏是否已更新(README 中日期是否为今天)
|
||||||
|
* 2. 每日自检是否执行(memory.json 中 daily_selfcheck 日期)
|
||||||
|
* 3. PSP 巡检是否执行
|
||||||
|
* 4. 大脑文件完整性
|
||||||
|
* 5. CI/CD 最近状态
|
||||||
|
* 6. 关键模块 README 是否存在
|
||||||
|
*
|
||||||
|
* 输出:
|
||||||
|
* - 控制台巡检报告
|
||||||
|
* - 更新 .github/brain/memory.json 写入巡检结果
|
||||||
|
* - 设置 GitHub Actions 输出变量供后续步骤使用
|
||||||
|
*
|
||||||
|
* 环境变量:
|
||||||
|
* GITHUB_TOKEN - GitHub API token
|
||||||
|
* GITHUB_REPOSITORY - owner/repo
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const MEMORY_PATH = path.join(ROOT, '.github', 'brain', 'memory.json');
|
||||||
|
const PERSONA_MEMORY_PATH = path.join(ROOT, '.github', 'persona-brain', 'memory.json');
|
||||||
|
const README_PATH = path.join(ROOT, 'README.md');
|
||||||
|
|
||||||
|
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
|
||||||
|
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const todayStr = now.toISOString().split('T')[0];
|
||||||
|
const todayShort = (() => {
|
||||||
|
const fmt = new Intl.DateTimeFormat('zh-CN', {
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
});
|
||||||
|
const parts = fmt.formatToParts(now);
|
||||||
|
const get = (type) => (parts.find(p => p.type === type) || {}).value || '';
|
||||||
|
return `${get('month')}-${get('day')}`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
/* ── 工具函数 ────────────────────────────── */
|
||||||
|
|
||||||
|
function loadJson(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveJson(filePath, data) {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function githubApi(endpoint) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!GITHUB_TOKEN) {
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = `https://api.github.com/repos/${REPO}${endpoint}`;
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'User-Agent': 'zhuyuan-agent',
|
||||||
|
},
|
||||||
|
timeout: 15000,
|
||||||
|
};
|
||||||
|
|
||||||
|
https.get(url, options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => { data += chunk; });
|
||||||
|
res.on('end', () => {
|
||||||
|
try { resolve(JSON.parse(data)); }
|
||||||
|
catch { resolve(null); }
|
||||||
|
});
|
||||||
|
}).on('error', () => resolve(null))
|
||||||
|
.on('timeout', function () { this.destroy(); resolve(null); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 巡检项 ──────────────────────────────── */
|
||||||
|
|
||||||
|
const checks = [];
|
||||||
|
const issues = [];
|
||||||
|
const actions = [];
|
||||||
|
|
||||||
|
// CHK-1: 公告栏是否有今天的条目
|
||||||
|
function checkBulletin() {
|
||||||
|
console.log('🔍 CHK-1: 检查公告栏更新...');
|
||||||
|
const readme = fs.existsSync(README_PATH) ? fs.readFileSync(README_PATH, 'utf8') : '';
|
||||||
|
const hasTodayEntry = readme.includes(todayShort);
|
||||||
|
|
||||||
|
if (hasTodayEntry) {
|
||||||
|
checks.push({ id: 'CHK-1', name: '公告栏更新', status: '✅', detail: `今日 (${todayShort}) 有更新条目` });
|
||||||
|
console.log(` ✅ 公告栏包含今日 (${todayShort}) 条目`);
|
||||||
|
} else {
|
||||||
|
checks.push({ id: 'CHK-1', name: '公告栏更新', status: '❌', detail: `今日 (${todayShort}) 无更新条目` });
|
||||||
|
issues.push('公告栏今日未更新');
|
||||||
|
actions.push('trigger_bulletin_update');
|
||||||
|
console.log(` ❌ 公告栏缺少今日 (${todayShort}) 条目`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHK-2: 每日自检是否执行
|
||||||
|
function checkDailySelfcheck() {
|
||||||
|
console.log('🔍 CHK-2: 检查每日自检...');
|
||||||
|
const memory = loadJson(PERSONA_MEMORY_PATH);
|
||||||
|
const lastRun = memory?.daily_selfcheck?.last_run || '';
|
||||||
|
const ranToday = lastRun.startsWith(todayStr);
|
||||||
|
|
||||||
|
if (ranToday) {
|
||||||
|
checks.push({ id: 'CHK-2', name: '每日自检', status: '✅', detail: `今日已执行 (${lastRun})` });
|
||||||
|
console.log(` ✅ 每日自检已执行 (${lastRun})`);
|
||||||
|
} else {
|
||||||
|
checks.push({ id: 'CHK-2', name: '每日自检', status: '⚠️', detail: `今日未执行,上次: ${lastRun || '无记录'}` });
|
||||||
|
issues.push('每日自检今日未执行');
|
||||||
|
actions.push('trigger_selfcheck');
|
||||||
|
console.log(` ⚠️ 每日自检今日未执行,上次: ${lastRun || '无记录'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHK-3: 大脑文件完整性
|
||||||
|
function checkBrainIntegrity() {
|
||||||
|
console.log('🔍 CHK-3: 检查大脑文件完整性...');
|
||||||
|
const requiredFiles = [
|
||||||
|
'.github/brain/memory.json',
|
||||||
|
'.github/persona-brain/memory.json',
|
||||||
|
];
|
||||||
|
const missing = [];
|
||||||
|
|
||||||
|
for (const f of requiredFiles) {
|
||||||
|
const fullPath = path.join(ROOT, f);
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
missing.push(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing.length === 0) {
|
||||||
|
checks.push({ id: 'CHK-3', name: '大脑文件完整性', status: '✅', detail: '所有核心文件完整' });
|
||||||
|
console.log(' ✅ 所有核心文件完整');
|
||||||
|
} else {
|
||||||
|
checks.push({ id: 'CHK-3', name: '大脑文件完整性', status: '❌', detail: `缺失: ${missing.join(', ')}` });
|
||||||
|
issues.push(`大脑文件缺失: ${missing.join(', ')}`);
|
||||||
|
console.log(` ❌ 大脑文件缺失: ${missing.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHK-4: CI 最近状态
|
||||||
|
async function checkCiStatus() {
|
||||||
|
console.log('🔍 CHK-4: 检查 CI 状态...');
|
||||||
|
const runs = await githubApi('/actions/runs?per_page=5&status=completed');
|
||||||
|
|
||||||
|
if (!runs || !runs.workflow_runs) {
|
||||||
|
checks.push({ id: 'CHK-4', name: 'CI 状态', status: '⚠️', detail: '无法获取 CI 状态' });
|
||||||
|
console.log(' ⚠️ 无法获取 CI 状态');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recent = runs.workflow_runs;
|
||||||
|
const failures = recent.filter(r => r.conclusion === 'failure');
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
checks.push({ id: 'CHK-4', name: 'CI 状态', status: '✅', detail: `最近 ${recent.length} 次运行全部成功` });
|
||||||
|
console.log(` ✅ 最近 ${recent.length} 次运行全部成功`);
|
||||||
|
} else {
|
||||||
|
const failNames = failures.map(f => f.name).join(', ');
|
||||||
|
checks.push({ id: 'CHK-4', name: 'CI 状态', status: '⚠️', detail: `${failures.length} 个失败: ${failNames}` });
|
||||||
|
issues.push(`CI 有 ${failures.length} 个失败工作流`);
|
||||||
|
console.log(` ⚠️ CI 有 ${failures.length} 个失败: ${failNames}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHK-5: 关键模块 README 检查
|
||||||
|
function checkModuleReadmes() {
|
||||||
|
console.log('🔍 CHK-5: 检查关键模块 README...');
|
||||||
|
const modules = [
|
||||||
|
'persona-studio', 'backend', 'backend-integration',
|
||||||
|
'status-board', 'dingtalk-bot', 'notification',
|
||||||
|
];
|
||||||
|
const missingReadme = [];
|
||||||
|
|
||||||
|
for (const mod of modules) {
|
||||||
|
const modDir = path.join(ROOT, mod);
|
||||||
|
if (!fs.existsSync(modDir)) continue;
|
||||||
|
const readmePath = path.join(modDir, 'README.md');
|
||||||
|
if (!fs.existsSync(readmePath)) {
|
||||||
|
missingReadme.push(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingReadme.length === 0) {
|
||||||
|
checks.push({ id: 'CHK-5', name: '模块 README', status: '✅', detail: '所有关键模块有 README' });
|
||||||
|
console.log(' ✅ 所有关键模块有 README');
|
||||||
|
} else {
|
||||||
|
checks.push({ id: 'CHK-5', name: '模块 README', status: '⚠️', detail: `缺失 README: ${missingReadme.join(', ')}` });
|
||||||
|
console.log(` ⚠️ 缺失 README: ${missingReadme.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHK-6: 公告栏更新工作流最近是否有失败
|
||||||
|
async function checkBulletinWorkflow() {
|
||||||
|
console.log('🔍 CHK-6: 检查公告栏更新工作流...');
|
||||||
|
const runs = await githubApi('/actions/workflows/update-readme-bulletin.yml/runs?per_page=3&status=completed');
|
||||||
|
|
||||||
|
if (!runs || !runs.workflow_runs) {
|
||||||
|
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '⚠️', detail: '无法获取工作流状态' });
|
||||||
|
console.log(' ⚠️ 无法获取工作流状态');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latest = runs.workflow_runs[0];
|
||||||
|
if (!latest) {
|
||||||
|
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '⚠️', detail: '无运行记录' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latest.conclusion === 'success') {
|
||||||
|
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '✅', detail: `最近一次成功 (${latest.created_at})` });
|
||||||
|
console.log(` ✅ 最近一次成功 (${latest.created_at})`);
|
||||||
|
} else {
|
||||||
|
checks.push({ id: 'CHK-6', name: '公告栏工作流', status: '❌', detail: `最近一次 ${latest.conclusion} (${latest.created_at})` });
|
||||||
|
issues.push(`公告栏工作流最近结论: ${latest.conclusion}`);
|
||||||
|
actions.push('trigger_bulletin_update');
|
||||||
|
console.log(` ❌ 最近一次 ${latest.conclusion} (${latest.created_at})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 主流程 ──────────────────────────────── */
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('═══════════════════════════════════════════');
|
||||||
|
console.log('🤖 铸渊每日巡检 Agent · ' + todayStr);
|
||||||
|
console.log('═══════════════════════════════════════════\n');
|
||||||
|
|
||||||
|
// 执行所有检查
|
||||||
|
checkBulletin();
|
||||||
|
checkDailySelfcheck();
|
||||||
|
checkBrainIntegrity();
|
||||||
|
await checkCiStatus();
|
||||||
|
checkModuleReadmes();
|
||||||
|
await checkBulletinWorkflow();
|
||||||
|
|
||||||
|
// 汇总
|
||||||
|
const passed = checks.filter(c => c.status === '✅').length;
|
||||||
|
const warnings = checks.filter(c => c.status === '⚠️').length;
|
||||||
|
const failed = checks.filter(c => c.status === '❌').length;
|
||||||
|
|
||||||
|
console.log('\n═══════════════════════════════════════════');
|
||||||
|
console.log('📊 巡检报告');
|
||||||
|
console.log('═══════════════════════════════════════════');
|
||||||
|
console.log(` ✅ 通过: ${passed} ⚠️ 警告: ${warnings} ❌ 失败: ${failed}`);
|
||||||
|
console.log(` 📋 总检查项: ${checks.length}`);
|
||||||
|
|
||||||
|
if (issues.length > 0) {
|
||||||
|
console.log('\n🔴 待处理问题:');
|
||||||
|
issues.forEach((issue, i) => console.log(` ${i + 1}. ${issue}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions.length > 0) {
|
||||||
|
console.log('\n🔧 建议自动修复:');
|
||||||
|
const uniqueActions = [...new Set(actions)];
|
||||||
|
uniqueActions.forEach(a => console.log(` → ${a}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 memory.json
|
||||||
|
const memory = loadJson(MEMORY_PATH) || {};
|
||||||
|
if (!memory.events) memory.events = [];
|
||||||
|
|
||||||
|
const summaryText = `铸渊巡检Agent · ✅${passed} ⚠️${warnings} ❌${failed}` +
|
||||||
|
(issues.length > 0 ? ` · ${issues.length}个问题` : ' · 全部通过');
|
||||||
|
|
||||||
|
memory.events.push({
|
||||||
|
type: 'daily_agent_inspection',
|
||||||
|
timestamp: now.toISOString(),
|
||||||
|
description: summaryText,
|
||||||
|
result: failed > 0 ? 'issues_found' : (warnings > 0 ? 'warnings' : 'passed'),
|
||||||
|
checks: checks.length,
|
||||||
|
passed,
|
||||||
|
warnings,
|
||||||
|
failed,
|
||||||
|
issues_detail: issues,
|
||||||
|
actions_suggested: [...new Set(actions)],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保留最近 50 条事件
|
||||||
|
if (memory.events.length > 50) {
|
||||||
|
memory.events = memory.events.slice(-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.last_agent_inspection = {
|
||||||
|
timestamp: now.toISOString(),
|
||||||
|
result: failed > 0 ? 'issues_found' : (warnings > 0 ? 'warnings' : 'passed'),
|
||||||
|
summary: summaryText,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveJson(MEMORY_PATH, memory);
|
||||||
|
console.log('\n💾 巡检结果已写入 memory.json');
|
||||||
|
|
||||||
|
// 输出 GitHub Actions 变量
|
||||||
|
const needBulletin = uniqueActions.includes('trigger_bulletin_update');
|
||||||
|
const needSelfcheck = uniqueActions.includes('trigger_selfcheck');
|
||||||
|
const outputFile = process.env.GITHUB_OUTPUT;
|
||||||
|
if (outputFile) {
|
||||||
|
const outputs = [
|
||||||
|
`has_issues=${issues.length > 0}`,
|
||||||
|
`issue_count=${issues.length}`,
|
||||||
|
`need_bulletin_update=${needBulletin}`,
|
||||||
|
`need_selfcheck=${needSelfcheck}`,
|
||||||
|
`summary=${summaryText}`,
|
||||||
|
];
|
||||||
|
fs.appendFileSync(outputFile, outputs.join('\n') + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ 铸渊巡检 Agent 完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('❌ 巡检 Agent 异常:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue