Add NE capability descriptors and native async exec jobs for agents.

Expose recommended_mode/hints on getManagedNe, support background exec-jobs with getNeExecJob in MCP, and allow linux_shell on MikroTik as well as Linux.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-06 18:32:14 +08:00
parent c6a98e2526
commit 8bdaaaacbf
20 changed files with 928 additions and 88 deletions

View file

@ -1640,9 +1640,9 @@ const en = {
},
execPolicy: {
readonly: "Read-only (show/display/ping)",
linuxShell: "Linux shell (single line)",
unrestricted: "Lab open (single line)",
hint: "Applies to MCP/API execManagedNe only. Default is read-only; shell / lab-open require a Linux device type.",
linuxShell: "Shell / script (multiline / heredoc OK)",
unrestricted: "Lab open (multiline / heredoc OK)",
hint: "Applies to MCP/API execManagedNe only. Default is read-only; Linux and MikroTik (routeros/switchos) may use shell / lab-open for multiline scripts and heredoc.",
},
source: {
manual: "Manual",

View file

@ -1627,9 +1627,9 @@ const zh = {
},
execPolicy: {
readonly: "只读(show/display/ping)",
linuxShell: "Linux shell(单行)",
unrestricted: "实验室全开(单行)",
hint: "仅影响 MCP/API 的 execManagedNe。默认只读;仅 Linux 设备类型可选 shell / 实验室全开。",
linuxShell: "Shell/脚本(允许多行/heredoc)",
unrestricted: "实验室全开(允许多行/heredoc)",
hint: "仅影响 MCP/API 的 execManagedNe。默认只读;Linux 与 MikroTik(routeros/switchos)可选 shell / 实验室全开,支持多行脚本与 heredoc。",
},
source: {
manual: "手工",

View file

@ -14,7 +14,7 @@ import {
buildManagedNeSaveBody,
emptyManagedNeForm,
formFromManagedNe,
isLinuxDeviceType,
allowsOpenExecPolicy,
type ManagedNeFormState,
} from "./formState";
@ -139,7 +139,7 @@ export function ManagedNeFormDialog({
setForm((prev) => ({
...prev,
device_type,
exec_policy: isLinuxDeviceType(device_type) ? prev.exec_policy : "readonly",
exec_policy: allowsOpenExecPolicy(device_type) ? prev.exec_policy : "readonly",
}));
}}
>
@ -162,7 +162,7 @@ export function ManagedNeFormDialog({
}
>
<option value="readonly">{t("managedNe.execPolicy.readonly")}</option>
{isLinuxDeviceType(form.device_type) ? (
{allowsOpenExecPolicy(form.device_type) ? (
<>
<option value="linux_shell">{t("managedNe.execPolicy.linuxShell")}</option>
<option value="unrestricted">{t("managedNe.execPolicy.unrestricted")}</option>

View file

@ -46,6 +46,22 @@ export function isLinuxDeviceType(deviceType: string | undefined | null): boolea
return low === "linux" || low === "linux_ssh" || low === "linux_telnet" || low.startsWith("linux_");
}
export function isMikrotikDeviceType(deviceType: string | undefined | null): boolean {
const low = String(deviceType || "")
.trim()
.toLowerCase();
return (
low === "mikrotik_routeros" ||
low === "mikrotik_switchos" ||
low.startsWith("mikrotik_")
);
}
/** Linux + MikroTik may use linux_shell / unrestricted (multiline scripts). */
export function allowsOpenExecPolicy(deviceType: string | undefined | null): boolean {
return isLinuxDeviceType(deviceType) || isMikrotikDeviceType(deviceType);
}
export function emptyManagedNeForm(): ManagedNeFormState {
return {
name: "",
@ -173,7 +189,7 @@ export function buildManagedNeSaveBody(
...(form.hop_password ? { hop_password: form.hop_password } : {}),
};
if (opts.execPolicyEnabled) {
body.exec_policy = isLinuxDeviceType(form.device_type) ? form.exec_policy : "readonly";
body.exec_policy = allowsOpenExecPolicy(form.device_type) ? form.exec_policy : "readonly";
}
if (form.hop_enabled) {
if (!form.hop_host.trim()) throw new Error(opts.hopHostRequired);