Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

475 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ═══════════════════════════════════════════════════════════
* 模块D+E · COS桶示警Agent + 三方对接 MCP 工具
* ═══════════════════════════════════════════════════════════
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 三方通信链路: 代码仓库 ↔ COS桶 ↔ Awen仓库
*
* 通信链路:
* Notion → 写工单到COS桶 /workorders/pending/
* COS桶变动 → 触发SCF云函数 → 唤醒代码仓库铸渊
* 铸渊整理技术需求 → 写入Awen的COS桶路径 /zhiqiu/tasks/
* Awen开发完成 → 回写COS桶 /zhiqiu/reports/ → 铸渊测试确认
*
* 工具清单:
* cosAlertScan — 扫描COS桶中的告警
* cosAlertResolve — 解决告警
* cosDispatchTask — 分发开发任务到Awen
* cosReadTaskReport — 读取Awen提交的任务报告
* cosListTaskReports — 列出所有任务报告
* cosApproveTask — 审批任务(通过/驳回)
* cosSendNotification — 发送通知(邮件/Issue
* cosGetCommLink — 获取通信链路状态
*/
'use strict';
const crypto = require('crypto');
const cos = require('../cos');
// ─── 路径常量 ───
const PATHS = {
ALERTS: 'zhuyuan/alerts/',
DIRECTIVES: 'zhuyuan/directives/',
WORKORDERS_PENDING: 'workorders/pending/',
WORKORDERS_PROCESSING: 'workorders/processing/',
WORKORDERS_COMPLETED: 'workorders/completed/',
AWEN_TASKS: 'zhiqiu/tasks/',
AWEN_REPORTS: 'zhiqiu/reports/',
AWEN_PROGRESS: 'zhiqiu/progress/',
NOTIFICATIONS: 'zhuyuan/notifications/'
};
/**
* cosAlertScan — 扫描COS桶中的告警
*
* input:
* bucket: string — 桶名默认team
* include_resolved: boolean — 是否包含已解决的告警
* limit: number — 最大数量
*/
async function cosAlertScan(input) {
const { bucket, include_resolved, limit } = input;
const targetBucket = bucket || 'team';
const result = await cos.list(targetBucket, PATHS.ALERTS, limit || 100);
const alertFiles = result.files.filter(f => f.key.endsWith('.json'));
// 读取每个告警的详情
const alerts = [];
for (const file of alertFiles.slice(0, 50)) {
try {
const raw = await cos.read(targetBucket, file.key);
const alert = JSON.parse(raw.content);
if (include_resolved || !alert.resolved) {
alerts.push(alert);
}
} catch {
// 跳过损坏的告警文件
}
}
// 按严重程度排序
const severityOrder = { critical: 0, warning: 1, info: 2 };
alerts.sort((a, b) => (severityOrder[a.severity] || 3) - (severityOrder[b.severity] || 3));
return {
alerts,
total: alerts.length,
critical: alerts.filter(a => a.severity === 'critical').length,
warning: alerts.filter(a => a.severity === 'warning').length,
info: alerts.filter(a => a.severity === 'info').length,
needs_bingshuo: alerts.filter(a => a.notify_bingshuo && !a.resolved).length
};
}
/**
* cosAlertResolve — 解决告警
*
* input:
* bucket: string — 桶名默认team
* alert_id: string — 告警ID
* resolution: string — 解决方案描述
* resolved_by: string — 解决者
*/
async function cosAlertResolve(input) {
const { bucket, alert_id, resolution, resolved_by } = input;
if (!alert_id) throw new Error('缺少 alert_id');
const targetBucket = bucket || 'team';
const key = `${PATHS.ALERTS}${alert_id}.json`;
// 读取现有告警
const raw = await cos.read(targetBucket, key);
const alert = JSON.parse(raw.content);
// 更新状态
alert.resolved = true;
alert.resolved_at = new Date().toISOString();
alert.resolution = resolution || '已处理';
alert.resolved_by = resolved_by || 'zhuyuan';
await cos.write(targetBucket, key, JSON.stringify(alert, null, 2), 'application/json');
return {
status: 'resolved',
alert_id,
resolution: alert.resolution,
resolved_by: alert.resolved_by
};
}
/**
* cosDispatchTask — 分发开发任务到Awen
*
* 铸渊整理技术需求后写入Awen的COS桶路径
*
* input:
* bucket: string — 桶名默认team
* task_id: string — 任务ID如 TASK-20260408-001
* title: string — 任务标题
* description: string — 任务描述
* priority: string — 优先级: critical|high|normal|low
* requirements: string[] — 技术需求列表
* workorder_id: string — 关联工单ID可选
* assigned_to: string — 指派给默认zhiqiu/Awen
* deadline: string — 截止日期(可选)
*/
async function cosDispatchTask(input) {
const {
bucket, task_id, title, description, priority,
requirements, workorder_id, assigned_to, deadline
} = input;
if (!title) throw new Error('缺少 title');
const targetBucket = bucket || 'team';
const tId = task_id || `TASK-${formatDate()}-${crypto.randomBytes(4).toString('hex')}`;
// 验证 task_id 安全性
if (!/^[a-zA-Z0-9_-]+$/.test(tId)) {
throw new Error('task_id 包含非法字符');
}
const now = new Date().toISOString();
const task = {
task_id: tId,
title,
description: description || '',
priority: priority || 'normal',
status: 'pending',
requirements: requirements || [],
workorder_id: workorder_id || null,
assigned_to: assigned_to || 'zhiqiu',
dispatched_by: 'zhuyuan',
dispatched_at: now,
deadline: deadline || null,
history: [{
action: 'dispatched',
timestamp: now,
by: 'zhuyuan',
note: '铸渊下发开发任务'
}]
};
const key = `${PATHS.AWEN_TASKS}${tId}.json`;
await cos.write(targetBucket, key, JSON.stringify(task, null, 2), 'application/json');
// 如果有关联工单,更新工单状态
if (workorder_id) {
try {
const woKey = `${PATHS.WORKORDERS_PENDING}${workorder_id}.json`;
const woRaw = await cos.read(targetBucket, woKey);
const workorder = JSON.parse(woRaw.content);
workorder.status = 'processing';
workorder.task_id = tId;
workorder.updated_at = now;
workorder.history.push({
action: 'dispatched_to_awen',
timestamp: now,
by: 'zhuyuan',
task_id: tId
});
// 移动到processing目录
await cos.write(targetBucket, `${PATHS.WORKORDERS_PROCESSING}${workorder_id}.json`,
JSON.stringify(workorder, null, 2), 'application/json');
await cos.del(targetBucket, woKey);
} catch {
// 工单不存在也不影响任务分发
}
}
return {
status: 'dispatched',
task_id: tId,
key,
bucket: targetBucket,
assigned_to: task.assigned_to,
priority: task.priority,
workorder_id: task.workorder_id
};
}
/**
* cosReadTaskReport — 读取Awen提交的任务报告
*
* input:
* bucket: string — 桶名默认team
* task_id: string — 任务ID
*/
async function cosReadTaskReport(input) {
const { bucket, task_id } = input;
if (!task_id) throw new Error('缺少 task_id');
const targetBucket = bucket || 'team';
const key = `${PATHS.AWEN_REPORTS}${task_id}.json`;
const raw = await cos.read(targetBucket, key);
return {
report: JSON.parse(raw.content),
key,
size_bytes: raw.size_bytes
};
}
/**
* cosListTaskReports — 列出所有任务报告
*
* input:
* bucket: string — 桶名默认team
* limit: number — 最大数量
*/
async function cosListTaskReports(input) {
const { bucket, limit } = input;
const targetBucket = bucket || 'team';
const result = await cos.list(targetBucket, PATHS.AWEN_REPORTS, limit || 100);
const reports = result.files
.filter(f => f.key.endsWith('.json'))
.map(f => ({
key: f.key,
task_id: f.key.split('/').pop().replace('.json', ''),
size_bytes: f.size_bytes
}));
return {
reports,
count: reports.length
};
}
/**
* cosApproveTask — 审批任务(通过/驳回)
*
* 铸渊测试确认后,通过/驳回Awen的开发报告
*
* input:
* bucket: string — 桶名默认team
* task_id: string — 任务ID
* approved: boolean — 是否通过
* feedback: string — 反馈意见
* next_task: object — 下一步任务(仅通过时有效)
*/
async function cosApproveTask(input) {
const { bucket, task_id, approved, feedback, next_task } = input;
if (!task_id) throw new Error('缺少 task_id');
const targetBucket = bucket || 'team';
const now = new Date().toISOString();
// 读取原始任务
let task;
try {
const taskRaw = await cos.read(targetBucket, `${PATHS.AWEN_TASKS}${task_id}.json`);
task = JSON.parse(taskRaw.content);
} catch {
task = { task_id, history: [] };
}
// 更新状态
task.status = approved ? 'approved' : 'rejected';
task.feedback = feedback || '';
task.reviewed_at = now;
task.reviewed_by = 'zhuyuan';
task.history.push({
action: approved ? 'approved' : 'rejected',
timestamp: now,
by: 'zhuyuan',
feedback: feedback || ''
});
// 写回任务文件
await cos.write(targetBucket, `${PATHS.AWEN_TASKS}${task_id}.json`,
JSON.stringify(task, null, 2), 'application/json');
// 写入审批回执
const receiptKey = `${PATHS.AWEN_PROGRESS}${task_id}-review.json`;
await cos.write(targetBucket, receiptKey, JSON.stringify({
task_id,
approved,
feedback: feedback || '',
reviewed_at: now,
reviewed_by: 'zhuyuan',
next_task: next_task || null
}, null, 2), 'application/json');
// 如果通过且有下一步任务,自动分发
let nextTaskResult = null;
if (approved && next_task) {
try {
nextTaskResult = await cosDispatchTask({
bucket: targetBucket,
title: next_task.title,
description: next_task.description,
priority: next_task.priority || 'normal',
requirements: next_task.requirements || [],
workorder_id: task.workorder_id
});
} catch (err) {
nextTaskResult = { error: err.message };
}
}
// 如果通过且有关联工单将工单移到completed
if (approved && task.workorder_id) {
try {
const woKey = `${PATHS.WORKORDERS_PROCESSING}${task.workorder_id}.json`;
const woRaw = await cos.read(targetBucket, woKey);
const workorder = JSON.parse(woRaw.content);
workorder.status = 'completed';
workorder.completed_at = now;
workorder.history.push({
action: 'completed',
timestamp: now,
by: 'zhuyuan',
task_id
});
await cos.write(targetBucket, `${PATHS.WORKORDERS_COMPLETED}${task.workorder_id}.json`,
JSON.stringify(workorder, null, 2), 'application/json');
await cos.del(targetBucket, woKey);
} catch { /* ignore */ }
}
return {
status: approved ? 'approved' : 'rejected',
task_id,
feedback: feedback || '',
receipt_key: receiptKey,
next_task: nextTaskResult
};
}
/**
* cosSendNotification — 发送通知
*
* 将通知写入COS桶由外部AgentGitHub Actions/SCF处理发送
*
* input:
* bucket: string — 桶名默认team
* notification_type: string — 通知类型: email|issue|cos_alert
* recipient: string — 接收者
* subject: string — 主题
* body: string — 内容
* metadata: object — 附加信息
*/
async function cosSendNotification(input) {
const { bucket, notification_type, recipient, subject, body, metadata } = input;
if (!notification_type || !subject) throw new Error('缺少 notification_type 或 subject');
const targetBucket = bucket || 'team';
const notificationId = `NOTIF-${Date.now()}`;
const now = new Date().toISOString();
const notification = {
notification_id: notificationId,
type: notification_type,
recipient: recipient || 'bingshuo',
subject,
body: body || '',
metadata: metadata || {},
status: 'pending',
created_at: now,
sent_at: null
};
const key = `${PATHS.NOTIFICATIONS}${notificationId}.json`;
await cos.write(targetBucket, key, JSON.stringify(notification, null, 2), 'application/json');
return {
notification_id: notificationId,
type: notification_type,
key,
status: 'queued',
note: '通知已入队等待发送Agent处理'
};
}
/**
* cosGetCommLink — 获取通信链路状态
*
* 检查三方通信链路的健康状态
*
* input:
* bucket: string — 桶名默认team
*/
async function cosGetCommLink(input) {
const { bucket } = input;
const targetBucket = bucket || 'team';
// 检查各路径是否可访问
const checks = {};
for (const [name, path] of Object.entries(PATHS)) {
try {
const result = await cos.list(targetBucket, path, 5);
checks[name] = {
status: 'accessible',
files: result.files.length
};
} catch (err) {
checks[name] = {
status: 'error',
error: err.message
};
}
}
// 统计待处理项
const pending = {
alerts: checks.ALERTS?.files || 0,
workorders: checks.WORKORDERS_PENDING?.files || 0,
task_reports: checks.AWEN_REPORTS?.files || 0,
notifications: checks.NOTIFICATIONS?.files || 0
};
return {
comm_link: 'zhuyuan-cos-awen',
bucket: targetBucket,
paths: checks,
pending,
health: Object.values(checks).every(c => c.status === 'accessible') ? 'healthy' : 'degraded',
timestamp: new Date().toISOString()
};
}
// ─── 辅助 ───
function formatDate() {
const d = new Date();
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
}
module.exports = {
cosAlertScan,
cosAlertResolve,
cosDispatchTask,
cosReadTaskReport,
cosListTaskReports,
cosApproveTask,
cosSendNotification,
cosGetCommLink
};