Files
SenMeshNet/electron/bridges/inspection/planGenerator.cjs
zhaolei a5744e720b
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
feat(plan-management): implement alert-remediation-plan system v1.3
- 8 new store modules + matcher + initialResponse + aggregation + remediation + AlertStateMonitor + planGenerator

- alert chain auto-integrated after runInspection: freeze snapshot + generate response + real-time agg + auto-start monitor

- AlertAIPanel component + MonitoringDetailPanel expandable alert list

- PlanManagementPage: full page with list/edit/AI generate/templates/commands
2026-10-30 16:55:55 +08:00

308 lines
10 KiB
JavaScript
Raw Permalink 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.
"use strict";
/**
* AI 预案生成器(planGenerator)
*
* 输入:运维一句话描述(如 "核心交换机 CPU 超 90% 持续 5 分钟,先 display cpu-usage,
* 持续不降就调阈值")
* 输出:符合 devicePlan schema 的 JSON 草稿,status=draft,走三重校验
*
* 三重校验(D14):
* ① schema 验证(基础字段完整性)
* ② commandCatalog 交叉校验(command_id 必须在目录中)
* ③ blocked_patterns 黑名单反查
*
* Prompt 上下文(D15):显式注入 command_catalog + catalog_type 字典,
* 让 AI 查表而不是瞎猜 device_type、command_id、template variable
*/
const { PLAN_STATUS } = require("./types.cjs");
const PLAN_GENERATION_SYSTEM_PROMPT = `你是 NetMesh 智能运维平台的 AI 预案生成专家。
根据运维一句话描述,生成结构化的设备预案草稿(device_plan),包含双轨:
① human_action_guide:值班员动作预案(人看的流程)
② remediation:操作级预案(机器执行的命令步骤)
【命令目录 - 你只能从这些里面选 command_id】
{{COMMAND_CATALOG_TABLE}}
【设备类型字典】
{{DEVICE_TYPES_TABLE}}
【告警级别】critical | warning | info
【严格 JSON 输出】
{
"device_type": "huawei_h3c_switch",
"level": "critical",
"description": "一句话预案说明",
"match": {
"metric_rules": [{ "metric_key": "cpu_usage", "op": ">", "value": 90 }],
"alert_types": ["performance_high"],
"device_tiers": ["core"]
},
"scope": { "host_ids": [], "host_groups": [] },
"human_action_guide": {
"receiving_alerts": ["..."],
"info_gathering": [{ "priority": 1, "action": "...", "purpose": "..." }],
"notification_chain": [{ "role": "...", "channel": "dingtalk", "trigger": "收到告警立即", "contact": "...", "auto": true }],
"escalation_triggers": [{ "condition": "...", "target_level": "critical" }],
"pre_remediation": ["..."],
"post_remediation": ["..."]
},
"remediation": {
"steps": [
{ "order": 1, "action": "command", "command_id": "switch_show_cpu", "execution": "auto", "on_success": "next", "on_failure": "stop" }
]
},
"security": {
"allowed_command_ids": ["switch_show_cpu"],
"allowed_commands": ["show *", "display *"],
"blocked_patterns": ["shutdown", "reload"]
}
}
【规则】
- command_id 必须从上面的命令目录中选取,严禁臆造
- 严禁输出 blocked_patterns 中的危险命令
- remediation steps 至少 1 条,最多 10 条
- 严禁输出外网 URL / 厂商文档链接
`;
/**
* 生成预案草稿
* @param {string} description - 运维自然语言描述
* @param {object} deps - { aiBridge, commandCatalogStore, devicePlanStore, templateStore }
* @returns {Promise<object>} { plan: devicePlan 草稿, warnings: [], validation: {} }
*/
async function generatePlan(description, deps) {
const { aiBridge, commandCatalogStore, devicePlanStore, templateStore } = deps;
if (!aiBridge?.providerConfigs?.some((p) => p.enabled !== false)) {
throw new Error("AI provider 未配置,无法生成预案");
}
// 构建 Prompt 上下文
const catalogTable = _buildCatalogTable(commandCatalogStore);
const deviceTypesTable = _buildDeviceTypesTable();
const systemPrompt = PLAN_GENERATION_SYSTEM_PROMPT
.replace("{{COMMAND_CATALOG_TABLE}}", catalogTable)
.replace("{{DEVICE_TYPES_TABLE}}", deviceTypesTable);
// 调 AI
let rawText;
try {
rawText = await _callAi(aiBridge, systemPrompt, description);
} catch (e) {
throw new Error(`AI 调用失败: ${e.message}`);
}
// 解析 JSON
let parsed;
try { parsed = _extractJson(rawText); }
catch { throw new Error("AI 返回内容无法解析为 JSON"); }
// 三重校验
const validation = _validatePlan(parsed, commandCatalogStore);
// 组装为 devicePlan(status=draft, source=ai)
const now = Date.now();
const draft = {
id: `plan_ai_${now}_${Math.random().toString(36).slice(2, 8)}`,
template_id: null,
device_type: parsed.device_type || "any",
level: parsed.level || "warning",
enabled: true,
builtin: false,
status: PLAN_STATUS.DRAFT,
source: "ai",
ai_provider: _detectProvider(aiBridge),
ai_prompt_version: "v2",
reject_reason: null,
review_log: [{ ts: now, actor: "system", action: "ai_generate", reason: null }],
description: parsed.description || description,
match: parsed.match || { metric_rules: [], alert_types: [], device_tiers: [], device_tags: [] },
scope: parsed.scope || { host_ids: [], host_groups: [] },
human_action_guide: parsed.human_action_guide || _emptyHumanActionGuide(),
remediation: parsed.remediation || { steps: [] },
security: parsed.security || { allowed_command_ids: [], allowed_commands: [], blocked_patterns: [] },
escalation_fallback: null,
required_role: "operator",
created_at: now,
updated_at: now,
};
return {
plan: draft,
warnings: validation.warnings,
validation: {
schema_valid: validation.schemaValid,
command_catalog_valid: validation.commandCatalogValid,
blocked_patterns_valid: validation.blockedPatternsValid,
},
};
}
function _buildCatalogTable(commandCatalogStore) {
let list = [];
try {
const _readFn = () => null; // 不依赖存储,用默认值
list = commandCatalogStore.listCommands(_readFn);
} catch {
list = [];
}
if (list.length === 0) return "(暂无预置命令目录,AI 只能用 show_cpu, show_mem, show_disk, show_version, collect_diagnostic, backup_config, adjust_threshold 等通用 id)";
let table = "| command_id | 设备类型 | 风险 | 权限 | 命令 |\n|---|---|---|---|---|\n";
for (const c of list) {
table += `| ${c.id} | ${c.device_types.join(", ")} | ${c.risk_level} | ${c.execution} | ${c.command} |\n`;
}
return table;
}
function _buildDeviceTypesTable() {
return "| device_type | 说明 |\n|---|---|\n| huawei_h3c_switch | 华为/H3C 交换机 |\n| cisco_switch | Cisco 交换机 |\n| huawei_firewall | 华为防火墙 |\n| fortinet_firewall | 飞塔防火墙 |\n| linux | Linux 服务器 |\n| windows | Windows 服务器 |";
}
async function _callAi(bridge, systemPrompt, userContent) {
const provider = (bridge?.providerConfigs || []).find((p) => p.enabled !== false);
if (!provider) throw new Error("no provider");
const resolved = bridge.resolveProviderApiKey(provider.id);
if (!resolved?.apiKey) throw new Error("api key not available");
const patched = bridge.injectApiKeyIntoRequest(
provider.endpoint || "https://api.minimaxi.com/v1/chat/completions",
{},
provider.id
);
const http = require("http"), https = require("https");
const u = new URL(patched.url);
const lib = patched.url.startsWith("https") ? https : http;
const bodyStr = JSON.stringify({
model: provider.model || "MiniMax-M1",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userContent },
],
temperature: 0.3,
max_tokens: 2000,
stream: false,
});
return await new Promise((resolve, reject) => {
const req = lib.request({
hostname: u.hostname, port: u.port || (u.protocol === "https:" ? 443 : 80),
path: u.pathname + u.search, method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${resolved.apiKey}` },
timeout: 60000,
}, (res) => {
let chunks = [];
res.on("data", (c) => chunks.push(c));
res.on("end", () => {
try {
const text = Buffer.concat(chunks).toString();
const parsed = JSON.parse(text);
const content = parsed.choices?.[0]?.message?.content
|| parsed.content?.[0]?.text
|| parsed.candidates?.[0]?.content?.parts?.[0]?.text || "";
resolve(content.trim());
} catch (e) { reject(e); }
});
});
req.on("error", reject);
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
req.write(bodyStr);
req.end();
});
}
function _extractJson(text) {
let cleaned = (text || "")
.replace(/```json\s*/gi, "")
.replace(/```\s*/g, "")
.replace(/<think>[\s\S]*?<\/think>/gi, "")
.trim();
try { return JSON.parse(cleaned); }
catch {
const m = cleaned.match(/\{[\s\S]*\}/);
if (m) return JSON.parse(m[0]);
throw new Error("no json found");
}
}
/**
* 三重校验(D14)
*/
function _validatePlan(plan, commandCatalogStore) {
const warnings = [];
let schemaValid = true;
let commandCatalogValid = true;
let blockedPatternsValid = true;
// ① schema 验证
if (!plan.device_type || !plan.level) {
schemaValid = false;
warnings.push("缺少 device_type 或 level");
}
if (!plan.remediation?.steps || plan.remediation.steps.length === 0) {
schemaValid = false;
warnings.push("remediation.steps 为空");
}
// ② command_catalog 交叉校验
const _readFn = () => null;
const commandIds = (plan.remediation?.steps || [])
.filter((s) => s.action === "command")
.map((s) => s.command_id)
.filter(Boolean);
if (commandIds.length > 0) {
const result = commandCatalogStore.validateCommandIds(_readFn, commandIds);
if (!result.valid) {
commandCatalogValid = false;
for (const invalidId of result.invalid) {
warnings.push(`command_id '${invalidId}' 不在预置命令目录中,需运维手动确认`);
}
}
}
// ③ blocked_patterns 黑名单反查
const blockedPatterns = plan.security?.blocked_patterns || [];
const allCommands = (plan.remediation?.steps || [])
.filter((s) => s.action === "command")
.map((s) => s.command || s.command_id);
for (const pat of blockedPatterns) {
for (const cmd of allCommands) {
if (typeof cmd === "string" && new RegExp(pat, "i").test(cmd)) {
blockedPatternsValid = false;
warnings.push(`命令 '${cmd}' 命中黑名单模式 '${pat}'`);
}
}
}
return { schemaValid, commandCatalogValid, blockedPatternsValid, warnings };
}
function _detectProvider(bridge) {
const p = (bridge?.providerConfigs || []).find((x) => x.enabled !== false);
return p ? p.id : null;
}
function _emptyHumanActionGuide() {
return {
receiving_alerts: [],
info_gathering: [],
notification_chain: [],
escalation_triggers: [],
pre_remediation: [],
post_remediation: [],
};
}
module.exports = {
generatePlan,
};