mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 05:10:45 +08:00
Sync dsh-netxops 0.1.41 with NetX async exec jobs and capability hints.
Add netx__getNeExecJob, route execManagedNe async to /exec-jobs, and document getManagedNe.capability for agents. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4dd628b7cc
commit
30d4df23fa
14 changed files with 740 additions and 58 deletions
|
|
@ -18,7 +18,7 @@ Model names: `netx__<stem>`. NMS tools use `Nms`; adapter `nmsProvider=zte-ume`
|
|||
| Tool | Role |
|
||||
|------|------|
|
||||
| `queryNmsAlarms` … `sqlQueryNms` | Alarm + inventory + SQL |
|
||||
| `listManagedNe` / `getManagedNe` / `execManagedNe` / `listCliTargets` | Managed CLI (login / show) |
|
||||
| `listManagedNe` / `getManagedNe` / `execManagedNe` / `getNeExecJob` / `listCliTargets` | Managed CLI (login / show / async jobs) |
|
||||
| `findTopologyPaths` | Fabric path lookup |
|
||||
|
||||
## bizMonitor → `netx-biz-monitor`
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
99
lib/index.js
99
lib/index.js
|
|
@ -589,6 +589,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -3392,7 +3393,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -3634,6 +3688,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -3667,7 +3723,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -3676,6 +3732,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -3698,7 +3761,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -3709,6 +3772,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -3718,6 +3788,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -4873,12 +4955,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -4887,7 +4969,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -4902,8 +4984,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dsh-netxops",
|
||||
"version": "0.1.40",
|
||||
"version": "0.1.41",
|
||||
"description": "DeepSeek Harness Netx Ops: ops/topology + IM delivery + session export + knowledge-base MANIFEST/_skills + localSkills",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
| 证据 | `queryNmsAlarmsRaw(field_preset=evidence)` |
|
||||
| 清单 | `queryNmsNeInventory` / `getNmsNe` |
|
||||
| 多台 CLI | `execManagedNe(nms_ne_ids=…)` 或 `targets` |
|
||||
| 长任务 / ≥4 台 | `execManagedNe(async=true)` → `getNeExecJob(job_id=…)` |
|
||||
| 设备能力 | `getManagedNe` → 读 `capability.recommended_mode` |
|
||||
| 路径 | `findTopologyPaths` |
|
||||
| A<>B 光 | 两端 host → 路径 → 多台一批 optic CLI |
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export const TOOLS_BY_GROUP: Readonly<Record<NetxCapabilityGroupId, readonly str
|
|||
'netx__listManagedNe',
|
||||
'netx__getManagedNe',
|
||||
'netx__execManagedNe',
|
||||
'netx__getNeExecJob',
|
||||
'netx__listCliTargets',
|
||||
'netx__findTopologyPaths',
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,55 @@
|
|||
import { type NetxClient, quoteNeId, type NetxJson } from './http.ts'
|
||||
import { omitUndefined } from './json-safe.ts'
|
||||
|
||||
const EXEC_MAX_COMMANDS = 5
|
||||
const EXEC_MAX_COMMANDS_CAP = 50
|
||||
const EXEC_MAX_COMMANDS_DEFAULT = 5
|
||||
|
||||
function execMaxCommands(): number {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ''), 10)
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n))
|
||||
}
|
||||
|
||||
function asyncMinNes(): number {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ''), 10)
|
||||
const n = Number.isFinite(raw) ? raw : 4
|
||||
return Math.max(0, Math.min(50, n))
|
||||
}
|
||||
|
||||
function truthyAsyncFlag(raw: unknown): boolean | undefined {
|
||||
if (raw === undefined || raw === null) return undefined
|
||||
if (typeof raw === 'boolean') return raw
|
||||
const text = String(raw).trim().toLowerCase()
|
||||
if (!text) return undefined
|
||||
if (['1', 'true', 'yes', 'on'].includes(text)) return true
|
||||
if (['0', 'false', 'no', 'off'].includes(text)) return false
|
||||
return undefined
|
||||
}
|
||||
|
||||
function countExecNeTargets(args: NetxJson): number {
|
||||
let n = 0
|
||||
for (const key of ['ne_ids', 'nms_ne_ids', 'ume_ne_ids'] as const) {
|
||||
const val = strList(args, key)
|
||||
if (val.length > 0) n = Math.max(n, val.length)
|
||||
}
|
||||
const targets = args.targets
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter(t => typeof t === 'object' && t !== null && !Array.isArray(t)).length)
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str(args, 'ne_id').trim() || nmsOrUme(args, 'nms_ne_id', 'ume_ne_id')) return 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
function shouldRunExecAsync(args: NetxJson): boolean {
|
||||
const flag = truthyAsyncFlag(args.async)
|
||||
if (flag === false) return false
|
||||
if (flag === true) return true
|
||||
const minN = asyncMinNes()
|
||||
if (minN <= 0) return false
|
||||
return countExecNeTargets(args) >= minN
|
||||
}
|
||||
|
||||
const UME_RAW_FIELD_PRESETS: Record<string, string[]> = {
|
||||
brief: [
|
||||
|
|
@ -218,6 +266,8 @@ export async function getManagedNe(client: NetxClient, args: NetxJson, signal?:
|
|||
}
|
||||
|
||||
export async function execManagedNe(client: NetxClient, args: NetxJson, signal?: AbortSignal): Promise<NetxJson> {
|
||||
const maxCmds = execMaxCommands()
|
||||
const wantAsync = shouldRunExecAsync(args)
|
||||
const targetsRaw = args.targets
|
||||
const neIds = strList(args, 'ne_ids')
|
||||
const umeNeIds = nmsOrUmeList(args, 'nms_ne_ids', 'ume_ne_ids')
|
||||
|
|
@ -249,7 +299,7 @@ export async function execManagedNe(client: NetxClient, args: NetxJson, signal?:
|
|||
if (neIds.length > 0) body.ne_ids = neIds
|
||||
if (umeNeIds.length > 0) body.ume_ne_ids = umeNeIds
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: 'too_many_commands', error_code: 'too_many_commands' }
|
||||
}
|
||||
body.commands = sharedCommands
|
||||
|
|
@ -257,6 +307,12 @@ export async function execManagedNe(client: NetxClient, args: NetxJson, signal?:
|
|||
body.read_timeout_sec = clampInt(num(args, 'read_timeout_sec'), 60, 10, 120)
|
||||
const concurrency = num(args, 'concurrency')
|
||||
if (concurrency !== undefined) body.concurrency = clampInt(concurrency, 4, 1, 8)
|
||||
if (wantAsync) {
|
||||
const out = await client.post('/v1/managed-ne/exec-jobs', body, signal, 60_000)
|
||||
if (out.ok !== true) return out
|
||||
const data = asRecord(out.data)
|
||||
return Object.keys(data).length > 0 ? data : out
|
||||
}
|
||||
const out = await client.post('/v1/managed-ne/exec-batch', body, signal, 600_000)
|
||||
if (out.ok !== true) return out
|
||||
const data = asRecord(out.data)
|
||||
|
|
@ -279,7 +335,7 @@ export async function execManagedNe(client: NetxClient, args: NetxJson, signal?:
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: 'commands_required', error_code: 'commands_required' }
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: 'too_many_commands', error_code: 'too_many_commands' }
|
||||
}
|
||||
const body: NetxJson = {
|
||||
|
|
@ -288,6 +344,12 @@ export async function execManagedNe(client: NetxClient, args: NetxJson, signal?:
|
|||
}
|
||||
if (neId) body.ne_id = neId
|
||||
if (umeNeId) body.ume_ne_id = umeNeId
|
||||
if (wantAsync) {
|
||||
const out = await client.post('/v1/managed-ne/exec-jobs', body, signal, 60_000)
|
||||
if (out.ok !== true) return out
|
||||
const data = asRecord(out.data)
|
||||
return Object.keys(data).length > 0 ? data : out
|
||||
}
|
||||
const out = await client.post('/v1/managed-ne/exec', body, signal, 300_000)
|
||||
if (out.ok !== true) return out
|
||||
const data = asRecord(out.data)
|
||||
|
|
@ -297,6 +359,19 @@ export async function execManagedNe(client: NetxClient, args: NetxJson, signal?:
|
|||
return { ok: true, data }
|
||||
}
|
||||
|
||||
export async function getNeExecJob(client: NetxClient, args: NetxJson, signal?: AbortSignal): Promise<NetxJson> {
|
||||
const jobId = str(args, 'job_id').trim()
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'job_id_required',
|
||||
error_code: 'job_id_required',
|
||||
hint: 'Pass job_id from execManagedNe async ack.',
|
||||
}
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal)
|
||||
}
|
||||
|
||||
export async function listCliTargets(client: NetxClient, args: NetxJson, signal?: AbortSignal): Promise<NetxJson> {
|
||||
const params: Record<string, string | number | boolean> = {
|
||||
page: clampInt(num(args, 'page'), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ export function registerNetxTools(
|
|||
),
|
||||
tool(
|
||||
'netx__getManagedNe',
|
||||
'Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.',
|
||||
'Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.',
|
||||
{
|
||||
ne_id: str('Managed NE id'),
|
||||
managed_ne_id: str('Alias for ne_id'),
|
||||
|
|
@ -263,7 +263,7 @@ export function registerNetxTools(
|
|||
),
|
||||
tool(
|
||||
'netx__execManagedNe',
|
||||
'Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.',
|
||||
'Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.',
|
||||
{
|
||||
ne_id: str(),
|
||||
nms_ne_id: str('NMS inventory id; alias ume_ne_id'),
|
||||
|
|
@ -273,7 +273,7 @@ export function registerNetxTools(
|
|||
ume_ne_ids: strArr('Legacy alias of nms_ne_ids'),
|
||||
targets: {
|
||||
type: 'array' as const,
|
||||
description: 'Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].',
|
||||
description: 'Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].',
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
additionalProperties: false,
|
||||
|
|
@ -288,10 +288,18 @@ export function registerNetxTools(
|
|||
commands: strArr('Commands for single NE, or shared commands for batch.'),
|
||||
read_timeout_sec: num('Per-command read timeout (default 60; use 90–120 for slow show).'),
|
||||
concurrency: num('Parallel NEs for batch mode (1–8, default 4).'),
|
||||
async: bool('oclaw-only async hint; ignored by native REST client.'),
|
||||
async: bool('true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).'),
|
||||
},
|
||||
H.execManagedNe, getClient, Math.max(t, 300_000),
|
||||
),
|
||||
tool(
|
||||
'netx__getNeExecJob',
|
||||
'Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.',
|
||||
{
|
||||
job_id: str('Job id from execManagedNe async response.'),
|
||||
},
|
||||
H.getNeExecJob, getClient, Math.max(t, 60_000),
|
||||
),
|
||||
tool(
|
||||
'netx__listCliTargets',
|
||||
'List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.',
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ test('one group one skill dir; ops owns NMS + managed CLI; topology owns canvas;
|
|||
})
|
||||
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__findTopologyPaths'))
|
||||
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__execManagedNe'))
|
||||
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__getNeExecJob'))
|
||||
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__queryNmsAlarms'))
|
||||
assert.ok(TOOLS_BY_GROUP.topology.includes('netx__getTopologyTree'))
|
||||
assert.ok(TOOLS_BY_GROUP.topology.includes('netx__layoutTopologyView'))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue