225 lines
7.0 KiB
JavaScript
225 lines
7.0 KiB
JavaScript
// scripts/neural/track-work-orders.js
|
||
// 🧬 工单追踪器
|
||
// 铸渊每次唤醒时运行,追踪所有活跃工单状态
|
||
// 检查超时、匹配回执、更新状态、生成追踪报告
|
||
//
|
||
// 指令回执追踪表配置(Notion 侧)
|
||
// 数据库位置:光湖语言壳 > 统一网关 > 指令回执追踪表
|
||
// 写入脚本:scripts/neural/write-receipt-to-notion.js
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
var WORK_ORDER_DIR = 'data/neural-reports/work-orders';
|
||
var COMPLETED_DIR = 'data/deploy-queue/completed';
|
||
var RECEIPT_DIR = 'data/neural-reports/receipts';
|
||
var TRACKER_DIR = 'data/neural-reports/work-orders';
|
||
var TERMINAL_STATUSES = ['CLOSED', 'ESCALATED', 'VERIFIED'];
|
||
|
||
// ━━━ 指令回执追踪配置 ━━━
|
||
var RECEIPT_TRACKER_CONFIG = {
|
||
// 指令回执追踪表 · Notion Database ID
|
||
// 位于:光湖语言壳 > 统一网关 > 指令回执追踪表
|
||
// 实际 Database ID 通过此环境变量名读取(安全考虑不硬编码)
|
||
databaseIdEnvVar: 'RECEIPT_DB_ID',
|
||
|
||
// 字段映射(与 Notion 数据库字段名一一对应)
|
||
fieldMap: {
|
||
instructionId: '指令编号', // title
|
||
summary: '指令摘要', // text
|
||
status: '执行状态', // status
|
||
receipt: '铸渊回执', // text
|
||
receiptTime: '回执时间', // date
|
||
newFiles: '新增文件数', // number
|
||
modifiedFiles: '修改文件数', // number
|
||
relatedAgent: '关联Agent', // text
|
||
timeoutStatus: '回执超时', // select
|
||
},
|
||
|
||
// 超时阈值(小时)
|
||
timeoutThresholds: {
|
||
warning: 24, // 24h → 🟡 接近超时
|
||
critical: 72, // 72h → 🔴 已超时
|
||
}
|
||
};
|
||
|
||
function loadJSON(p) {
|
||
try { return JSON.parse(fs.readFileSync(p, 'utf8')); }
|
||
catch (e) { return null; }
|
||
}
|
||
|
||
function getActiveWorkOrders() {
|
||
if (!fs.existsSync(WORK_ORDER_DIR)) return [];
|
||
|
||
var allOrders = [];
|
||
var files = fs.readdirSync(WORK_ORDER_DIR)
|
||
.filter(function(f) { return f.startsWith('work-orders-') && f.endsWith('.json'); });
|
||
|
||
for (var i = 0; i < files.length; i++) {
|
||
var data = loadJSON(path.join(WORK_ORDER_DIR, files[i]));
|
||
if (data && data.work_orders) {
|
||
for (var j = 0; j < data.work_orders.length; j++) {
|
||
allOrders.push(data.work_orders[j]);
|
||
}
|
||
}
|
||
}
|
||
|
||
return allOrders.filter(function(wo) {
|
||
return TERMINAL_STATUSES.indexOf(wo.status) === -1;
|
||
});
|
||
}
|
||
|
||
function getCompletedReceipts() {
|
||
var receipts = {};
|
||
|
||
// 从 deploy-queue/completed 收集
|
||
if (fs.existsSync(COMPLETED_DIR)) {
|
||
var files = fs.readdirSync(COMPLETED_DIR)
|
||
.filter(function(f) { return f.endsWith('.json'); });
|
||
|
||
for (var i = 0; i < files.length; i++) {
|
||
var receipt = loadJSON(path.join(COMPLETED_DIR, files[i]));
|
||
if (receipt && receipt.command_id) {
|
||
receipts[receipt.command_id] = receipt;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 从 neural-reports/receipts 收集本地回执
|
||
if (fs.existsSync(RECEIPT_DIR)) {
|
||
var receiptFiles = fs.readdirSync(RECEIPT_DIR)
|
||
.filter(function(f) { return f.endsWith('.json'); });
|
||
|
||
for (var j = 0; j < receiptFiles.length; j++) {
|
||
var localReceipt = loadJSON(path.join(RECEIPT_DIR, receiptFiles[j]));
|
||
if (localReceipt && localReceipt.instruction_id) {
|
||
receipts[localReceipt.instruction_id] = localReceipt;
|
||
}
|
||
}
|
||
}
|
||
|
||
return receipts;
|
||
}
|
||
|
||
function checkTimeout(workOrder) {
|
||
var timeout = workOrder.timeout_hours || RECEIPT_TRACKER_CONFIG.timeoutThresholds.critical;
|
||
var created = new Date(workOrder.created);
|
||
var now = new Date();
|
||
var elapsed = (now - created) / (1000 * 60 * 60);
|
||
return elapsed > timeout;
|
||
}
|
||
|
||
function getTimeoutStatus(workOrder) {
|
||
var created = new Date(workOrder.created);
|
||
var now = new Date();
|
||
var elapsed = (now - created) / (1000 * 60 * 60);
|
||
|
||
if (elapsed > RECEIPT_TRACKER_CONFIG.timeoutThresholds.critical) return '🔴 已超时';
|
||
if (elapsed > RECEIPT_TRACKER_CONFIG.timeoutThresholds.warning) return '🟡 接近超时';
|
||
return '🟢 正常';
|
||
}
|
||
|
||
function trackWorkOrders() {
|
||
console.log('\n━━━ 🧬 工单追踪器启动 ━━━\n');
|
||
|
||
var activeOrders = getActiveWorkOrders();
|
||
var receipts = getCompletedReceipts();
|
||
|
||
console.log('活跃工单: ' + activeOrders.length + ' 个');
|
||
|
||
var stats = {
|
||
total_active: activeOrders.length,
|
||
by_status: {},
|
||
timed_out: 0,
|
||
matched_receipts: 0,
|
||
escalated: 0
|
||
};
|
||
|
||
for (var i = 0; i < activeOrders.length; i++) {
|
||
var wo = activeOrders[i];
|
||
var status = wo.status || 'pending';
|
||
|
||
// 统计状态分布
|
||
if (!stats.by_status[status]) stats.by_status[status] = 0;
|
||
stats.by_status[status]++;
|
||
|
||
// 检查超时
|
||
if (checkTimeout(wo)) {
|
||
stats.timed_out++;
|
||
console.log(' ⏰ 超时: ' + wo.id + ' (' + wo.title + ')');
|
||
|
||
// 检查是否需要升级
|
||
var maxRetries = (wo.constraints && wo.constraints.max_retries) || 2;
|
||
if (wo.retry_count >= maxRetries) {
|
||
wo.status = 'ESCALATED';
|
||
stats.escalated++;
|
||
console.log(' 🔺 升级到人类: ' + wo.id);
|
||
}
|
||
}
|
||
|
||
// 检查回执匹配
|
||
if (wo.command_id && receipts[wo.command_id]) {
|
||
var receipt = receipts[wo.command_id];
|
||
stats.matched_receipts++;
|
||
|
||
if (receipt.result && receipt.result.status === 'success') {
|
||
wo.status = 'VERIFIED';
|
||
console.log(' ✅ 已验证: ' + wo.id + ' (回执成功)');
|
||
} else if (receipt.result && receipt.result.status === 'failure') {
|
||
wo.status = 'pending';
|
||
wo.retry_count = (wo.retry_count || 0) + 1;
|
||
console.log(' ❌ 失败: ' + wo.id + ' (重试 ' + wo.retry_count + ')');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 生成追踪报告
|
||
var now = new Date();
|
||
var cstTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
|
||
var dateStr = cstTime.toISOString().split('T')[0];
|
||
|
||
var trackerReport = {
|
||
tracker_id: 'TRACKER-' + dateStr,
|
||
timestamp: now.toISOString(),
|
||
receipt_config: {
|
||
database_env_var: RECEIPT_TRACKER_CONFIG.databaseIdEnvVar,
|
||
timeout_warning_hours: RECEIPT_TRACKER_CONFIG.timeoutThresholds.warning,
|
||
timeout_critical_hours: RECEIPT_TRACKER_CONFIG.timeoutThresholds.critical
|
||
},
|
||
stats: stats,
|
||
active_orders: activeOrders.map(function(wo) {
|
||
return {
|
||
id: wo.id,
|
||
severity: wo.severity,
|
||
title: wo.title,
|
||
status: wo.status,
|
||
created: wo.created,
|
||
timed_out: checkTimeout(wo),
|
||
timeout_status: getTimeoutStatus(wo)
|
||
};
|
||
})
|
||
};
|
||
|
||
fs.mkdirSync(TRACKER_DIR, { recursive: true });
|
||
fs.writeFileSync(
|
||
path.join(TRACKER_DIR, 'tracker-' + dateStr + '.json'),
|
||
JSON.stringify(trackerReport, null, 2)
|
||
);
|
||
|
||
console.log('\n📊 追踪报告:');
|
||
console.log(' 活跃: ' + stats.total_active);
|
||
console.log(' 超时: ' + stats.timed_out);
|
||
console.log(' 回执匹配: ' + stats.matched_receipts);
|
||
console.log(' 升级: ' + stats.escalated);
|
||
console.log(' 状态分布: ' + JSON.stringify(stats.by_status));
|
||
console.log('\n━━━ 追踪完成 ━━━\n');
|
||
|
||
return trackerReport;
|
||
}
|
||
|
||
if (require.main === module) {
|
||
trackWorkOrders();
|
||
}
|
||
|
||
module.exports = { trackWorkOrders, RECEIPT_TRACKER_CONFIG };
|