zhizhi/server/age-os/mcp-server/tools/cos-comm-ops.js

475 lines
14 KiB
JavaScript
Raw Normal View History

/**
*
* 模块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
};