netxops/lib/index.js
oliver e8b7d7331f Show key-alarm WSS status on the Netx Ops Plugins card (0.1.16).
Host publishes connection phases over Connection RPC; the card polls and badges Connected / Reconnecting / Auth failed so operators need not curl logs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 21:08:14 +08:00

558 lines
18 KiB
JavaScript

import { createRequire } from "node:module";
var __require = /* @__PURE__ */ createRequire(import.meta.url);
// src/index.ts
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { homedir as homedir2 } from "node:os";
import { dirname, join as join2 } from "node:path";
import { fileURLToPath } from "node:url";
import z from "@deepseek-ai/schemastery";
import { credentialRef } from "@deepseek-ai/dsh-credentials";
import * as DshSettings from "@deepseek-ai/dsh-settings";
// src/netx/alarm-push-status.ts
var STORE_KEY = Symbol.for("dsh-netxops.alarm-push-status");
function emptyStatus(partial = {}) {
return {
phase: "disabled",
enabled: false,
wsUrl: "",
detail: "",
updatedAt: Date.now(),
lastConnectedAt: null,
lastError: null,
...partial
};
}
function store() {
const root = globalThis;
let current = root[STORE_KEY];
if (current === undefined) {
current = { status: emptyStatus(), listeners: new Set };
root[STORE_KEY] = current;
}
return current;
}
function getAlarmPushStatus() {
return { ...store().status };
}
function publishAlarmPushStatus(next) {
const state = store();
state.status = {
...state.status,
...next,
updatedAt: Date.now()
};
for (const listener of state.listeners)
listener();
}
function resetAlarmPushStatus() {
publishAlarmPushStatus(emptyStatus({ phase: "disabled", enabled: false }));
}
// src/netx/alarm-push.ts
function alarmSubscribeUrl(apiUrl) {
const trimmed = apiUrl.trim().replace(/\/$/, "");
if (!trimmed)
return "";
let url;
try {
url = new URL(trimmed);
} catch {
return "";
}
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
url.pathname = "/v1/integrations/dsh-alarm/ws";
url.search = "";
url.hash = "";
return url.toString();
}
function formatAlarmPrompt(payload, lang = "zh") {
const action = String(payload.action ?? "").trim().toLowerCase();
const actionZh = {
inserted: "告警产生",
updated: "告警更新",
deleted: "告警清除"
};
const actionEn = {
inserted: "Alarm Raised",
updated: "Alarm Updated",
deleted: "Alarm Cleared"
};
const ne = payload.ne && typeof payload.ne === "object" ? payload.ne : {};
const host = String(ne.host_name ?? "").trim();
const ip = String(ne.ip_address ?? "").trim();
const neName = String(ne.ne_name ?? ne.user_label ?? "").trim();
let device = host || neName || String(payload.ne_id ?? "").trim() || "-";
if (ip)
device = device === "-" ? ip : `${device} (${ip})`;
const label = String(payload.rule_label ?? payload.native_probable_cause ?? "关键告警").trim();
if (lang.startsWith("en")) {
return [
`[UME ${actionEn[action] ?? (action || "Alarm")}] ${label}`,
`Device: ${device}`,
`Object: ${String(payload.object_name ?? "-").trim()}`,
`Severity: ${String(payload.perceived_severity ?? "-").trim()}`,
`Cause: ${String(payload.native_probable_cause ?? "-").trim()}`,
`Time: ${String(payload.time_created ?? "-").trim()}`,
`notificationId: ${String(payload.notification_id ?? "-").trim()}`,
`alarm_key: ${String(payload.alarm_key ?? "-").trim()}`,
"",
"Please analyze this key alarm and suggest next ops steps."
].join(`
`);
}
return [
`[UME ${actionZh[action] ?? (action || "告警")}] ${label}`,
`设备: ${device}`,
`对象: ${String(payload.object_name ?? "-").trim()}`,
`级别: ${String(payload.perceived_severity ?? "-").trim()}`,
`原因: ${String(payload.native_probable_cause ?? "-").trim()}`,
`时间: ${String(payload.time_created ?? "-").trim()}`,
`notificationId: ${String(payload.notification_id ?? "-").trim()}`,
`alarm_key: ${String(payload.alarm_key ?? "-").trim()}`,
"",
"请分析这条关键告警并给出下一步运维建议。"
].join(`
`);
}
function setPhase(phase, wsUrl, extra = {}) {
publishAlarmPushStatus({
phase,
enabled: true,
wsUrl,
detail: extra.detail ?? "",
lastError: extra.lastError === undefined ? null : extra.lastError,
...extra.lastConnectedAt !== undefined ? { lastConnectedAt: extra.lastConnectedAt } : {}
});
}
function startAlarmPushClient(options) {
const log = options.logger ?? console;
const wsUrl = alarmSubscribeUrl(options.apiUrl);
const token = options.token.trim();
if (!wsUrl || !token) {
log.warn?.("netxops alarm-push: missing apiUrl or token — not connecting");
setPhase("error", wsUrl, {
detail: "missing_url_or_token",
lastError: "missing apiUrl or token"
});
return () => {};
}
const WS = options.WebSocketImpl ?? globalThis.WebSocket;
if (typeof WS !== "function") {
log.error?.("netxops alarm-push: WebSocket is unavailable in this runtime");
setPhase("error", wsUrl, {
detail: "websocket_unavailable",
lastError: "WebSocket unavailable"
});
return () => {};
}
let closed = false;
let socket = null;
let reconnectTimer;
let attempt = 0;
const baseDelay = Math.max(500, options.reconnectMs ?? 2000);
const clearReconnect = () => {
if (reconnectTimer !== undefined) {
clearTimeout(reconnectTimer);
reconnectTimer = undefined;
}
};
const scheduleReconnect = (reason) => {
if (closed)
return;
clearReconnect();
const delay = Math.min(60000, baseDelay * 2 ** Math.min(attempt, 5));
attempt += 1;
setPhase("reconnecting", wsUrl, {
detail: `retry_in_${delay}ms`,
lastError: reason
});
reconnectTimer = setTimeout(() => {
connect();
}, delay);
};
const connect = () => {
if (closed)
return;
clearReconnect();
setPhase(attempt > 0 ? "reconnecting" : "connecting", wsUrl, { detail: "dialing" });
try {
socket = new WS(wsUrl);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
log.warn?.("netxops alarm-push: connect failed:", error);
scheduleReconnect(message);
return;
}
socket.addEventListener("open", () => {
setPhase("authenticating", wsUrl, { detail: "auth" });
socket?.send(JSON.stringify({ type: "auth", token }));
});
socket.addEventListener("message", (event) => {
let msg;
try {
msg = JSON.parse(String(event.data));
} catch {
return;
}
const type = String(msg.type ?? "").toLowerCase();
if (type === "auth-ok") {
attempt = 0;
const now = Date.now();
setPhase("connected", wsUrl, {
detail: String(msg.user ?? "ok"),
lastError: null,
lastConnectedAt: now
});
log.info?.("netxops alarm-push: subscribed to %s", wsUrl);
return;
}
if (type === "auth-fail") {
const err = String(msg.error ?? "auth_failed");
log.error?.("netxops alarm-push: auth failed (%s)", err);
setPhase("auth_failed", wsUrl, { detail: err, lastError: err });
socket?.close();
return;
}
if (type === "pong")
return;
if (type === "event" && String(msg.event ?? "") === "netx.alarm") {
const payload = msg.payload && typeof msg.payload === "object" ? msg.payload : {};
Promise.resolve(options.onAlarm(payload)).catch((error) => {
log.warn?.("netxops alarm-push: handler failed:", error);
});
}
});
socket.addEventListener("close", () => {
socket = null;
if (!closed)
scheduleReconnect("socket_closed");
});
socket.addEventListener("error", () => {});
};
const pingTimer = setInterval(() => {
if (socket && socket.readyState === WS.OPEN) {
try {
socket.send(JSON.stringify({ type: "ping", ts: new Date().toISOString() }));
} catch {}
}
}, 25000);
connect();
return () => {
closed = true;
clearReconnect();
clearInterval(pingTimer);
try {
socket?.close();
} catch {}
socket = null;
};
}
// src/netx/alarm-session.ts
import { homedir } from "node:os";
import { join } from "node:path";
var PRESET_ID = "netxops";
var PERMISSION_PRESET = "default";
var TITLE = "Netx 关键告警";
var sticky = null;
function resolveWorkspacePath() {
const fromEnv = process.env.DSH_HOME?.trim();
const home = fromEnv && fromEnv.length > 0 ? fromEnv : join(homedir(), ".dsh");
return join(home, "workspaces", "netxops-alarms");
}
async function deliverAlarmToSession(ctx, payload, lang = "zh") {
const prompt = formatAlarmPrompt(payload, lang);
const agents = ctx.agents;
if (!agents || typeof agents.create !== "function") {
ctx.logger.warn("netxops alarm-push: ctx.agents unavailable — enable a profile that mounts agents to receive alarms in a DSH session");
return;
}
if (sticky?.agent && typeof sticky.agent.followup === "function") {
try {
await followup(ctx, sticky.agent, prompt);
return;
} catch (error) {
ctx.logger.warn("netxops alarm-push: sticky followup failed, recreating session: %s", error);
sticky = null;
}
}
await createStickySession(ctx, prompt);
}
async function followup(ctx, agent, prompt) {
let createUserMessage;
try {
const mod = await import("@deepseek-ai/dsh-llm");
createUserMessage = mod.createUserMessage;
if (typeof createUserMessage !== "function")
throw new Error("createUserMessage missing");
const summary = typeof mod.boundContextSummary === "function" ? mod.boundContextSummary("netx key alarm") : "netx key alarm";
agent.followup(createUserMessage({
content: [{ type: "text", text: prompt }],
source: {
kind: "webhook",
provider: "netx",
source: "dsh-alarm-hub",
form: "notice",
summary
}
}));
} catch (error) {
const anyAgent = agent;
if (typeof anyAgent.prompt === "function") {
await anyAgent.prompt(prompt);
return;
}
ctx.logger.warn("netxops alarm-push: cannot build user message (%s)", error);
throw error;
}
}
async function createStickySession(ctx, prompt) {
const c = ctx;
const agents = c.agents;
const agentPresets = c.agentPresets;
const workspaceRegistry = c.workspaceRegistry;
const permissionPresets = c.permissionPresets;
const sessionTitle = c.sessionTitle;
const agentDefaultModel = c.agentDefaultModel;
if (!agentPresets || typeof agentPresets.resolve !== "function" || !workspaceRegistry || typeof workspaceRegistry.create !== "function") {
ctx.logger.warn("netxops alarm-push: agentPresets/workspaceRegistry unavailable — cannot create a DSH session");
return;
}
if (permissionPresets && typeof permissionPresets.resolve === "function") {
try {
permissionPresets.resolve(PERMISSION_PRESET);
} catch {
ctx.logger.warn("netxops alarm-push: permission preset %s missing", PERMISSION_PRESET);
}
}
const preset = await agentPresets.resolve(PRESET_ID);
if (typeof agentPresets.standingKeyFor === "function") {
await agentPresets.standingKeyFor(preset.id);
}
const selected = typeof agentDefaultModel?.currentSelection === "function" ? agentDefaultModel.currentSelection() : { provider: "deepseek", model: "deepseek-chat" };
const workspacePath = resolveWorkspacePath();
const workspace = await workspaceRegistry.create(workspacePath);
const sessionId = `netxops-alarm-${Date.now().toString(36)}`;
const handle = await agents.create({
sessionId,
meta: { cwd: workspace.path, agentPreset: preset.id },
agentOptions: { provider: selected.provider, model: selected.model },
setup: async (agentCtx) => {
if (typeof agentPresets.mount === "function") {
await agentPresets.mount(agentCtx, preset.id);
}
}
});
try {
if (typeof workspace.attachSession === "function") {
await workspace.attachSession(sessionId);
}
if (permissionPresets && typeof permissionPresets.set === "function") {
permissionPresets.set(handle.agent.session, PERMISSION_PRESET);
}
if (sessionTitle && typeof sessionTitle.rename === "function") {
sessionTitle.rename(handle.agent.session, TITLE);
}
await followup(ctx, handle.agent, prompt);
sticky = { sessionId, agent: handle.agent };
ctx.logger.info("netxops alarm-push: opened sticky session %s", sessionId);
} catch (error) {
try {
await handle.dispose?.();
} catch {}
throw error;
}
}
function resetAlarmSession() {
sticky = null;
}
// src/netx/runtime.ts
var STORE_KEY2 = Symbol.for("dsh-netxops.connection-store");
function store2() {
const root = globalThis;
let current = root[STORE_KEY2];
if (current === undefined) {
current = { connection: undefined, listeners: new Set };
root[STORE_KEY2] = current;
}
return current;
}
function publishNetxConnection(next) {
const state = store2();
state.connection = next;
for (const listener of state.listeners)
listener();
}
// src/index.ts
var name = "netxops";
var inject = ["credentials"];
var NETXOPS_RPC_CHANNEL = "/netxops";
var NETXOPS_SETTINGS_NAMESPACE = "netxops";
var NETXOPS_PRESET_ID = "netxops";
var DEFAULT_TOKEN_REF = "NETX_API_TOKEN";
var Config = z.object({
apiUrl: z.string().default("http://127.0.0.1:8890"),
lang: z.string().default("zh"),
tokenCredentialRef: z.string().role("credential-ref").default(DEFAULT_TOKEN_REF),
toolCallTimeoutMs: z.number().step(1).min(1000).default(120000),
installAgentPreset: z.boolean().default(true),
alarmPushEnabled: z.boolean().default(false)
});
function packageRoot() {
return join2(dirname(fileURLToPath(import.meta.url)), "..");
}
function resolveDshHome() {
const fromEnv = process.env.DSH_HOME?.trim();
if (fromEnv !== undefined && fromEnv.length > 0)
return fromEnv;
return join2(homedir2(), ".dsh");
}
function ensureAgentPresetInstalled(logger) {
const src = join2(packageRoot(), "presets", NETXOPS_PRESET_ID);
const composition = join2(src, "agent.cordis.yml");
if (!existsSync(composition)) {
logger.warn("netxops: bundled preset missing at %s — skip user-preset install", src);
return;
}
const destParent = join2(resolveDshHome(), ".agent-presets");
const dest = join2(destParent, NETXOPS_PRESET_ID);
try {
mkdirSync(destParent, { recursive: true });
if (existsSync(dest)) {
rmSync(dest, { recursive: true, force: true });
}
cpSync(src, dest, { recursive: true });
writeFileSync(join2(dest, ".dsh-netxops-managed"), `${new Date().toISOString()}
`, "utf8");
logger.info("netxops: agent preset installed at %s", dest);
} catch (error) {
logger.error("netxops: failed to install agent preset: %s", error);
}
}
async function resolveToken(ctx, refName) {
const hit = await ctx.credentials.resolve(credentialRef(refName));
return hit?.value ?? "";
}
function installNetxopsSettings(ctx, entry, hooks) {
const legacy = DshSettings.installSettingsSection;
if (typeof legacy === "function") {
legacy(ctx, NETXOPS_SETTINGS_NAMESPACE, Config, entry, hooks);
return;
}
ctx.inject(["settings"], (settingsCtx) => {
settingsCtx.settings.installSection(ctx, NETXOPS_SETTINGS_NAMESPACE, Config, entry, hooks);
});
}
function apply(ctx, config = Config({})) {
let source = () => config;
let publishing = Promise.resolve();
let generation = 0;
let stopAlarmPush;
if (config.installAgentPreset) {
ensureAgentPresetInstalled(ctx.logger);
}
const restartAlarmPush = (apiUrl, token, enabled, lang) => {
stopAlarmPush?.();
stopAlarmPush = undefined;
resetAlarmSession();
if (!enabled) {
resetAlarmPushStatus();
return;
}
if (!token.trim()) {
ctx.logger.warn("netxops alarm-push: enabled but token is empty — not connecting");
publishAlarmPushStatus({
phase: "error",
enabled: true,
wsUrl: "",
detail: "missing_token",
lastError: "token empty"
});
return;
}
stopAlarmPush = startAlarmPushClient({
apiUrl,
token,
logger: ctx.logger,
onAlarm: (payload) => deliverAlarmToSession(ctx, payload, lang)
});
};
const publish = () => {
publishing = publishing.then(async () => {
const gen = ++generation;
const current = source();
const token = await resolveToken(ctx, current.tokenCredentialRef);
if (gen !== generation)
return;
const apiUrl = current.apiUrl.replace(/\/$/, "");
const tokenConfigured = token.trim().length > 0;
publishNetxConnection({
apiUrl,
token,
lang: current.lang,
toolCallTimeoutMs: current.toolCallTimeoutMs
});
restartAlarmPush(apiUrl, token, current.alarmPushEnabled === true, current.lang);
if (!tokenConfigured) {
ctx.logger.warn("netxops: published connection → %s tokenConfigured=false (set credential %s)", apiUrl, current.tokenCredentialRef);
} else {
ctx.logger.info("netxops: published connection → %s tokenConfigured=true alarmPush=%s", apiUrl, current.alarmPushEnabled === true);
}
}).catch((error) => {
ctx.logger.error("netxops: connection publish error: %s", error);
});
};
publish();
installNetxopsSettings(ctx, config, {
setSource: (current) => {
source = current;
},
onChange: () => {
publish();
}
});
ctx.on("credentials/reference-updated", (ref) => {
if (String(ref) === source().tokenCredentialRef)
publish();
});
ctx.inject(["connection"], (connCtx) => {
const rpc = connCtx.connection?.rpc;
if (!rpc || typeof rpc.handle !== "function") {
connCtx.logger.warn("netxops: connection.rpc.handle unavailable — alarm status UI disabled");
return;
}
connCtx.effect(() => {
const dispose = rpc.handle(NETXOPS_RPC_CHANNEL, async (endpoint) => {
if (endpoint !== "alarm-push.status") {
return { ok: false, error: { code: "bad-request", message: "Unknown endpoint." } };
}
return { ok: true, value: getAlarmPushStatus() };
});
return () => {
dispose();
};
}, "netxops: alarm-push status rpc");
});
ctx.effect(() => () => {
generation += 1;
stopAlarmPush?.();
stopAlarmPush = undefined;
resetAlarmSession();
resetAlarmPushStatus();
}, "netxops: dispose host bridge");
}
export {
name,
inject,
ensureAgentPresetInstalled,
apply,
NETXOPS_SETTINGS_NAMESPACE,
NETXOPS_RPC_CHANNEL,
NETXOPS_PRESET_ID,
DEFAULT_TOKEN_REF,
Config
};