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>
This commit is contained in:
oliver 2026-09-05 21:08:14 +08:00
parent 12f7018103
commit e8b7d7331f
14 changed files with 783 additions and 55 deletions

View file

@ -10,6 +10,46 @@ 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(/\/$/, "");
@ -76,17 +116,35 @@ function formatAlarmPrompt(payload, lang = "zh") {
].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;
@ -100,12 +158,16 @@ function startAlarmPushClient(options) {
reconnectTimer = undefined;
}
};
const scheduleReconnect = () => {
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);
@ -114,15 +176,17 @@ function startAlarmPushClient(options) {
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();
scheduleReconnect(message);
return;
}
socket.addEventListener("open", () => {
attempt = 0;
setPhase("authenticating", wsUrl, { detail: "auth" });
socket?.send(JSON.stringify({ type: "auth", token }));
});
socket.addEventListener("message", (event) => {
@ -134,11 +198,20 @@ function startAlarmPushClient(options) {
}
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") {
log.error?.("netxops alarm-push: auth failed (%s)", msg.error);
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;
}
@ -154,7 +227,7 @@ function startAlarmPushClient(options) {
socket.addEventListener("close", () => {
socket = null;
if (!closed)
scheduleReconnect();
scheduleReconnect("socket_closed");
});
socket.addEventListener("error", () => {});
};
@ -297,18 +370,18 @@ function resetAlarmSession() {
}
// src/netx/runtime.ts
var STORE_KEY = Symbol.for("dsh-netxops.connection-store");
function store() {
var STORE_KEY2 = Symbol.for("dsh-netxops.connection-store");
function store2() {
const root = globalThis;
let current = root[STORE_KEY];
let current = root[STORE_KEY2];
if (current === undefined) {
current = { connection: undefined, listeners: new Set };
root[STORE_KEY] = current;
root[STORE_KEY2] = current;
}
return current;
}
function publishNetxConnection(next) {
const state = store();
const state = store2();
state.connection = next;
for (const listener of state.listeners)
listener();
@ -317,6 +390,7 @@ function publishNetxConnection(next) {
// 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";
@ -385,10 +459,19 @@ function apply(ctx, config = Config({})) {
stopAlarmPush?.();
stopAlarmPush = undefined;
resetAlarmSession();
if (!enabled)
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({
@ -436,11 +519,30 @@ function apply(ctx, config = Config({})) {
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 {
@ -449,6 +551,7 @@ export {
ensureAgentPresetInstalled,
apply,
NETXOPS_SETTINGS_NAMESPACE,
NETXOPS_RPC_CHANNEL,
NETXOPS_PRESET_ID,
DEFAULT_TOKEN_REF,
Config