diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 41029e0..53bde4c 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,4 +1,4 @@ -# Install Netx Ops on DeepSeek Harness +# Install Netx Ops on DeepSeek Harness ## What `dsh plugin add` gives you @@ -16,9 +16,9 @@ You do **not** run `link-preset.ps1` for normal use. That script is only a manua ## Still required outside the npm package 1. **netx API** reachable (default `http://127.0.0.1:8890`) — the data plane. -2. **API token** with scopes matching the tools you use (`alarms:read`, `ne:read`, `ne:exec`, `sql:query`, …). +2. **API token** with scopes matching the tools you use (`alarms:read`, `ne:read`, `ne:exec`, `biz-monitor:read`, `sql:query`, …). -No local Python / `netx_mcp` install is required for DSH. (OpenClaw and other MCP hosts can still use `python -m netx_mcp` separately.) +No local Python / `netx_mcp` install is required for DSH. (OpenClaw and other MCP hosts can still use `python -m netx_mcp` separately for NMS/CLI — **not** for biz-monitor; that is netxops host tools only.) ## Clone / `pnpm install`(仓库源码) @@ -67,7 +67,7 @@ One command, three **direct** profile bundles. Do **not** nest IM/cron under net 1. Plugins card **Netx Ops** visible. 2. With key-alarm push on: card badge shows **Connected** (or reconnecting / auth failed with detail). 3. Agent presets → Custom → **Netx Ops**. -4. Tools include `netx__queryNmsAlarms` / `netx__execManagedNe` / `netx__findTopologyPaths` (**ops** → skill `netx-ops`). Canvas / dual_unit need **topology** (`netx-topology`). +4. Tools include `netx__queryNmsAlarms` / `netx__execManagedNe` / `netx__findTopologyPaths` (**ops** → skill `netx-ops`). Cutover analysis: `netx__getBizMonitorContext` … (**bizMonitor** → `netx-biz-monitor`). Canvas / dual_unit need **topology** (`netx-topology`). 5. Export section shows a session count; **下载 ZIP** starts a browser download. 6. Optional knowledge-base badge shows operator/country/version when `kbRoot` + MANIFEST are valid. diff --git a/docs/TOOL_MAP.md b/docs/TOOL_MAP.md index d43c18e..5b474be 100644 --- a/docs/TOOL_MAP.md +++ b/docs/TOOL_MAP.md @@ -6,9 +6,10 @@ Tools register by capability group. **One group ↔ one skill.** |-------|-------|------------------------| | **ops** | `netx-ops` | on | | **topology** | `netx-topology` | off | +| **bizMonitor** | `netx-biz-monitor` | on | Public flags mount the same group on the host for other presets. -Forced mounts: `dsh-netxops/tools-ops` | `tools-topology` (legacy aliases: `tools-nms` / `tools-common` → ops). +Forced mounts: `dsh-netxops/tools-ops` | `tools-topology` | `tools-biz-monitor` (legacy aliases: `tools-nms` / `tools-common` → ops). Model names: `netx__`. NMS tools use `Nms`; adapter `nmsProvider=zte-ume` still hits `/v1/ume/*`. @@ -20,6 +21,17 @@ Model names: `netx__`. NMS tools use `Nms`; adapter `nmsProvider=zte-ume` | `listManagedNe` / `getManagedNe` / `execManagedNe` / `listCliTargets` | Managed CLI (login / show) | | `findTopologyPaths` | Fabric path lookup | +## bizMonitor → `netx-biz-monitor` + +Host tools call netx REST directly (**not** netx-mcp). Token needs `biz-monitor:read`. + +| Tool | Role | +|------|------| +| `getBizMonitorContext` | Project + templates + normalize + port map + tasks | +| `getBizMonitorBoard` | Batch / evaluate progress | +| `listBizMonitorReds` / `getBizMonitorDiffs` | Reds & diffs with evidence (raw A/B + show cmd) | +| `getBizCollectBatch` / `getBizCollectCommandRaw` | Collect audit + full CLI raw | + ## topology → `netx-topology` | Tool | Role | diff --git a/lib/agent-tools-common.js b/lib/agent-tools-common.js index ff7357f..87e839a 100644 --- a/lib/agent-tools-common.js +++ b/lib/agent-tools-common.js @@ -1,7 +1,8 @@ // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), - topology: Object.freeze({ inPreset: false, public: false }) + topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var TOOLS_BY_GROUP = Object.freeze({ ops: Object.freeze([ @@ -37,15 +38,25 @@ var TOOLS_BY_GROUP = Object.freeze({ "netx__suggestSinkHubs", "netx__analyzeTopologyViewLayout", "netx__sinkTopologyDualUnits" + ]), + bizMonitor: Object.freeze([ + "netx__getBizMonitorContext", + "netx__getBizMonitorBoard", + "netx__listBizMonitorReds", + "netx__getBizMonitorDiffs", + "netx__getBizCollectBatch", + "netx__getBizCollectCommandRaw" ]) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", - topology: "topology" + topology: "topology", + bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", - "topology" + "topology", + "bizMonitor" ]); function groupsForPlane(groups, plane, only) { const policy = groups ?? DEFAULT_CAPABILITY_GROUPS; @@ -60,12 +71,12 @@ function groupsForced(only) { return CAPABILITY_GROUP_IDS.filter((id) => normalized.includes(id)); } function toolNamesForGroups(groupIds) { - const names = new Set; + const out = new Set; for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) - names.add(tool); + for (const name of TOOLS_BY_GROUP[id] ?? []) + out.add(name); } - return names; + return out; } // src/netx/group-skills.ts @@ -1458,6 +1469,68 @@ async function findTopologyPaths(client, args, signal) { body.to_managed_ne_id = toMid; return client.post("/v1/topology/fabric/paths", body, signal, 30000); } +async function getBizMonitorContext(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + const taskId = str2(args, "task_id").trim(); + if (!projectId && !taskId) { + return { ok: false, error: "project_id_or_task_id_required" }; + } + const params = {}; + if (projectId) + params.project_id = projectId; + if (taskId) + params.task_id = taskId; + return client.get("/v1/biz-migration/monitor-context", params, signal); +} +async function getBizMonitorBoard(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + const params = {}; + const runId = str2(args, "run_id").trim(); + if (runId) + params.run_id = runId; + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal); +} +async function listBizMonitorReds(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + if (!projectId) + return { ok: false, error: "project_id_required" }; + const params = {}; + putStr(params, args, ["status"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + return client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, params, signal); +} +async function getBizMonitorDiffs(client, args, signal) { + const runId = str2(args, "run_id").trim(); + if (!runId) + return { ok: false, error: "run_id_required" }; + const params = {}; + putStr(params, args, ["metric_id", "sheet_id", "verdict", "color", "q"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + const offset = num2(args, "offset"); + if (offset !== undefined) + params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER); + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal); +} +async function getBizCollectBatch(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal); +} +async function getBizCollectCommandRaw(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + const commandId = str2(args, "command_id").trim(); + if (!batchId || !commandId) { + return { ok: false, error: "batch_id_and_command_id_required" }; + } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, {}, signal); +} // src/netx/topology-handlers.ts function asRecord2(value) { @@ -2282,6 +2355,36 @@ function registerNetxTools(ctx, connection, options) { layer: str4(), detail: str4("summary | full") }, findTopologyPaths, getClient, t), + tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.", { + project_id: str4("Cutover / biz-migration project id"), + task_id: str4("biz_state task id (optional if project_id set)") + }, getBizMonitorContext, getClient, t), + tool2("netx__getBizMonitorBoard", "Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.", { + batch_id: { type: "string", required: true }, + run_id: str4("Optional evaluate run id") + }, getBizMonitorBoard, getClient, t), + tool2("netx__listBizMonitorReds", "Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).", { + project_id: { type: "string", required: true }, + status: str4("open|carried|resolved or empty=all"), + limit: num4() + }, listBizMonitorReds, getClient, t), + tool2("netx__getBizMonitorDiffs", "Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.", { + run_id: { type: "string", required: true }, + metric_id: str4(), + sheet_id: str4(), + verdict: str4(), + color: str4("e.g. red"), + q: str4(), + limit: num4(), + offset: num4() + }, getBizMonitorDiffs, getClient, t), + tool2("netx__getBizCollectBatch", "One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.", { + batch_id: { type: "string", required: true } + }, getBizCollectBatch, getClient, t), + tool2("netx__getBizCollectCommandRaw", "Full CLI raw_text for one collect command inside a batch (deep dive).", { + batch_id: { type: "string", required: true }, + command_id: { type: "string", required: true } + }, getBizCollectCommandRaw, getClient, t), tool2("netx__getTopologyTree", "Get topology folder tree (nav roots + Root map canvases). Start here before createTopologyFolder.", { compact: bool4(), max_depth: num4() }, getTopologyTree, getClient, t), tool2("netx__getTopologyView", "Get a topology view by view_id. Default detail=summary with sample_nodes + links[].", { view_id: { type: "string", required: true }, diff --git a/lib/agent-tools-nms.js b/lib/agent-tools-nms.js index 3fc88b0..25dc70f 100644 --- a/lib/agent-tools-nms.js +++ b/lib/agent-tools-nms.js @@ -1,7 +1,8 @@ // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), - topology: Object.freeze({ inPreset: false, public: false }) + topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var TOOLS_BY_GROUP = Object.freeze({ ops: Object.freeze([ @@ -37,15 +38,25 @@ var TOOLS_BY_GROUP = Object.freeze({ "netx__suggestSinkHubs", "netx__analyzeTopologyViewLayout", "netx__sinkTopologyDualUnits" + ]), + bizMonitor: Object.freeze([ + "netx__getBizMonitorContext", + "netx__getBizMonitorBoard", + "netx__listBizMonitorReds", + "netx__getBizMonitorDiffs", + "netx__getBizCollectBatch", + "netx__getBizCollectCommandRaw" ]) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", - topology: "topology" + topology: "topology", + bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", - "topology" + "topology", + "bizMonitor" ]); function groupsForPlane(groups, plane, only) { const policy = groups ?? DEFAULT_CAPABILITY_GROUPS; @@ -60,12 +71,12 @@ function groupsForced(only) { return CAPABILITY_GROUP_IDS.filter((id) => normalized.includes(id)); } function toolNamesForGroups(groupIds) { - const names = new Set; + const out = new Set; for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) - names.add(tool); + for (const name of TOOLS_BY_GROUP[id] ?? []) + out.add(name); } - return names; + return out; } // src/netx/group-skills.ts @@ -1458,6 +1469,68 @@ async function findTopologyPaths(client, args, signal) { body.to_managed_ne_id = toMid; return client.post("/v1/topology/fabric/paths", body, signal, 30000); } +async function getBizMonitorContext(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + const taskId = str2(args, "task_id").trim(); + if (!projectId && !taskId) { + return { ok: false, error: "project_id_or_task_id_required" }; + } + const params = {}; + if (projectId) + params.project_id = projectId; + if (taskId) + params.task_id = taskId; + return client.get("/v1/biz-migration/monitor-context", params, signal); +} +async function getBizMonitorBoard(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + const params = {}; + const runId = str2(args, "run_id").trim(); + if (runId) + params.run_id = runId; + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal); +} +async function listBizMonitorReds(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + if (!projectId) + return { ok: false, error: "project_id_required" }; + const params = {}; + putStr(params, args, ["status"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + return client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, params, signal); +} +async function getBizMonitorDiffs(client, args, signal) { + const runId = str2(args, "run_id").trim(); + if (!runId) + return { ok: false, error: "run_id_required" }; + const params = {}; + putStr(params, args, ["metric_id", "sheet_id", "verdict", "color", "q"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + const offset = num2(args, "offset"); + if (offset !== undefined) + params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER); + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal); +} +async function getBizCollectBatch(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal); +} +async function getBizCollectCommandRaw(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + const commandId = str2(args, "command_id").trim(); + if (!batchId || !commandId) { + return { ok: false, error: "batch_id_and_command_id_required" }; + } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, {}, signal); +} // src/netx/topology-handlers.ts function asRecord2(value) { @@ -2282,6 +2355,36 @@ function registerNetxTools(ctx, connection, options) { layer: str4(), detail: str4("summary | full") }, findTopologyPaths, getClient, t), + tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.", { + project_id: str4("Cutover / biz-migration project id"), + task_id: str4("biz_state task id (optional if project_id set)") + }, getBizMonitorContext, getClient, t), + tool2("netx__getBizMonitorBoard", "Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.", { + batch_id: { type: "string", required: true }, + run_id: str4("Optional evaluate run id") + }, getBizMonitorBoard, getClient, t), + tool2("netx__listBizMonitorReds", "Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).", { + project_id: { type: "string", required: true }, + status: str4("open|carried|resolved or empty=all"), + limit: num4() + }, listBizMonitorReds, getClient, t), + tool2("netx__getBizMonitorDiffs", "Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.", { + run_id: { type: "string", required: true }, + metric_id: str4(), + sheet_id: str4(), + verdict: str4(), + color: str4("e.g. red"), + q: str4(), + limit: num4(), + offset: num4() + }, getBizMonitorDiffs, getClient, t), + tool2("netx__getBizCollectBatch", "One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.", { + batch_id: { type: "string", required: true } + }, getBizCollectBatch, getClient, t), + tool2("netx__getBizCollectCommandRaw", "Full CLI raw_text for one collect command inside a batch (deep dive).", { + batch_id: { type: "string", required: true }, + command_id: { type: "string", required: true } + }, getBizCollectCommandRaw, getClient, t), tool2("netx__getTopologyTree", "Get topology folder tree (nav roots + Root map canvases). Start here before createTopologyFolder.", { compact: bool4(), max_depth: num4() }, getTopologyTree, getClient, t), tool2("netx__getTopologyView", "Get a topology view by view_id. Default detail=summary with sample_nodes + links[].", { view_id: { type: "string", required: true }, diff --git a/lib/agent-tools-ops.js b/lib/agent-tools-ops.js index 0a9adad..9da224f 100644 --- a/lib/agent-tools-ops.js +++ b/lib/agent-tools-ops.js @@ -1,7 +1,8 @@ // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), - topology: Object.freeze({ inPreset: false, public: false }) + topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var TOOLS_BY_GROUP = Object.freeze({ ops: Object.freeze([ @@ -37,15 +38,25 @@ var TOOLS_BY_GROUP = Object.freeze({ "netx__suggestSinkHubs", "netx__analyzeTopologyViewLayout", "netx__sinkTopologyDualUnits" + ]), + bizMonitor: Object.freeze([ + "netx__getBizMonitorContext", + "netx__getBizMonitorBoard", + "netx__listBizMonitorReds", + "netx__getBizMonitorDiffs", + "netx__getBizCollectBatch", + "netx__getBizCollectCommandRaw" ]) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", - topology: "topology" + topology: "topology", + bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", - "topology" + "topology", + "bizMonitor" ]); function groupsForPlane(groups, plane, only) { const policy = groups ?? DEFAULT_CAPABILITY_GROUPS; @@ -60,12 +71,12 @@ function groupsForced(only) { return CAPABILITY_GROUP_IDS.filter((id) => normalized.includes(id)); } function toolNamesForGroups(groupIds) { - const names = new Set; + const out = new Set; for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) - names.add(tool); + for (const name of TOOLS_BY_GROUP[id] ?? []) + out.add(name); } - return names; + return out; } // src/netx/group-skills.ts @@ -1458,6 +1469,68 @@ async function findTopologyPaths(client, args, signal) { body.to_managed_ne_id = toMid; return client.post("/v1/topology/fabric/paths", body, signal, 30000); } +async function getBizMonitorContext(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + const taskId = str2(args, "task_id").trim(); + if (!projectId && !taskId) { + return { ok: false, error: "project_id_or_task_id_required" }; + } + const params = {}; + if (projectId) + params.project_id = projectId; + if (taskId) + params.task_id = taskId; + return client.get("/v1/biz-migration/monitor-context", params, signal); +} +async function getBizMonitorBoard(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + const params = {}; + const runId = str2(args, "run_id").trim(); + if (runId) + params.run_id = runId; + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal); +} +async function listBizMonitorReds(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + if (!projectId) + return { ok: false, error: "project_id_required" }; + const params = {}; + putStr(params, args, ["status"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + return client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, params, signal); +} +async function getBizMonitorDiffs(client, args, signal) { + const runId = str2(args, "run_id").trim(); + if (!runId) + return { ok: false, error: "run_id_required" }; + const params = {}; + putStr(params, args, ["metric_id", "sheet_id", "verdict", "color", "q"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + const offset = num2(args, "offset"); + if (offset !== undefined) + params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER); + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal); +} +async function getBizCollectBatch(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal); +} +async function getBizCollectCommandRaw(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + const commandId = str2(args, "command_id").trim(); + if (!batchId || !commandId) { + return { ok: false, error: "batch_id_and_command_id_required" }; + } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, {}, signal); +} // src/netx/topology-handlers.ts function asRecord2(value) { @@ -2282,6 +2355,36 @@ function registerNetxTools(ctx, connection, options) { layer: str4(), detail: str4("summary | full") }, findTopologyPaths, getClient, t), + tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.", { + project_id: str4("Cutover / biz-migration project id"), + task_id: str4("biz_state task id (optional if project_id set)") + }, getBizMonitorContext, getClient, t), + tool2("netx__getBizMonitorBoard", "Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.", { + batch_id: { type: "string", required: true }, + run_id: str4("Optional evaluate run id") + }, getBizMonitorBoard, getClient, t), + tool2("netx__listBizMonitorReds", "Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).", { + project_id: { type: "string", required: true }, + status: str4("open|carried|resolved or empty=all"), + limit: num4() + }, listBizMonitorReds, getClient, t), + tool2("netx__getBizMonitorDiffs", "Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.", { + run_id: { type: "string", required: true }, + metric_id: str4(), + sheet_id: str4(), + verdict: str4(), + color: str4("e.g. red"), + q: str4(), + limit: num4(), + offset: num4() + }, getBizMonitorDiffs, getClient, t), + tool2("netx__getBizCollectBatch", "One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.", { + batch_id: { type: "string", required: true } + }, getBizCollectBatch, getClient, t), + tool2("netx__getBizCollectCommandRaw", "Full CLI raw_text for one collect command inside a batch (deep dive).", { + batch_id: { type: "string", required: true }, + command_id: { type: "string", required: true } + }, getBizCollectCommandRaw, getClient, t), tool2("netx__getTopologyTree", "Get topology folder tree (nav roots + Root map canvases). Start here before createTopologyFolder.", { compact: bool4(), max_depth: num4() }, getTopologyTree, getClient, t), tool2("netx__getTopologyView", "Get a topology view by view_id. Default detail=summary with sample_nodes + links[].", { view_id: { type: "string", required: true }, diff --git a/lib/agent-tools-topology.js b/lib/agent-tools-topology.js index dad42ed..295479e 100644 --- a/lib/agent-tools-topology.js +++ b/lib/agent-tools-topology.js @@ -1,7 +1,8 @@ // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), - topology: Object.freeze({ inPreset: false, public: false }) + topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var TOOLS_BY_GROUP = Object.freeze({ ops: Object.freeze([ @@ -37,15 +38,25 @@ var TOOLS_BY_GROUP = Object.freeze({ "netx__suggestSinkHubs", "netx__analyzeTopologyViewLayout", "netx__sinkTopologyDualUnits" + ]), + bizMonitor: Object.freeze([ + "netx__getBizMonitorContext", + "netx__getBizMonitorBoard", + "netx__listBizMonitorReds", + "netx__getBizMonitorDiffs", + "netx__getBizCollectBatch", + "netx__getBizCollectCommandRaw" ]) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", - topology: "topology" + topology: "topology", + bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", - "topology" + "topology", + "bizMonitor" ]); function groupsForPlane(groups, plane, only) { const policy = groups ?? DEFAULT_CAPABILITY_GROUPS; @@ -60,12 +71,12 @@ function groupsForced(only) { return CAPABILITY_GROUP_IDS.filter((id) => normalized.includes(id)); } function toolNamesForGroups(groupIds) { - const names = new Set; + const out = new Set; for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) - names.add(tool); + for (const name of TOOLS_BY_GROUP[id] ?? []) + out.add(name); } - return names; + return out; } // src/netx/group-skills.ts @@ -1458,6 +1469,68 @@ async function findTopologyPaths(client, args, signal) { body.to_managed_ne_id = toMid; return client.post("/v1/topology/fabric/paths", body, signal, 30000); } +async function getBizMonitorContext(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + const taskId = str2(args, "task_id").trim(); + if (!projectId && !taskId) { + return { ok: false, error: "project_id_or_task_id_required" }; + } + const params = {}; + if (projectId) + params.project_id = projectId; + if (taskId) + params.task_id = taskId; + return client.get("/v1/biz-migration/monitor-context", params, signal); +} +async function getBizMonitorBoard(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + const params = {}; + const runId = str2(args, "run_id").trim(); + if (runId) + params.run_id = runId; + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal); +} +async function listBizMonitorReds(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + if (!projectId) + return { ok: false, error: "project_id_required" }; + const params = {}; + putStr(params, args, ["status"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + return client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, params, signal); +} +async function getBizMonitorDiffs(client, args, signal) { + const runId = str2(args, "run_id").trim(); + if (!runId) + return { ok: false, error: "run_id_required" }; + const params = {}; + putStr(params, args, ["metric_id", "sheet_id", "verdict", "color", "q"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + const offset = num2(args, "offset"); + if (offset !== undefined) + params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER); + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal); +} +async function getBizCollectBatch(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal); +} +async function getBizCollectCommandRaw(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + const commandId = str2(args, "command_id").trim(); + if (!batchId || !commandId) { + return { ok: false, error: "batch_id_and_command_id_required" }; + } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, {}, signal); +} // src/netx/topology-handlers.ts function asRecord2(value) { @@ -2282,6 +2355,36 @@ function registerNetxTools(ctx, connection, options) { layer: str4(), detail: str4("summary | full") }, findTopologyPaths, getClient, t), + tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.", { + project_id: str4("Cutover / biz-migration project id"), + task_id: str4("biz_state task id (optional if project_id set)") + }, getBizMonitorContext, getClient, t), + tool2("netx__getBizMonitorBoard", "Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.", { + batch_id: { type: "string", required: true }, + run_id: str4("Optional evaluate run id") + }, getBizMonitorBoard, getClient, t), + tool2("netx__listBizMonitorReds", "Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).", { + project_id: { type: "string", required: true }, + status: str4("open|carried|resolved or empty=all"), + limit: num4() + }, listBizMonitorReds, getClient, t), + tool2("netx__getBizMonitorDiffs", "Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.", { + run_id: { type: "string", required: true }, + metric_id: str4(), + sheet_id: str4(), + verdict: str4(), + color: str4("e.g. red"), + q: str4(), + limit: num4(), + offset: num4() + }, getBizMonitorDiffs, getClient, t), + tool2("netx__getBizCollectBatch", "One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.", { + batch_id: { type: "string", required: true } + }, getBizCollectBatch, getClient, t), + tool2("netx__getBizCollectCommandRaw", "Full CLI raw_text for one collect command inside a batch (deep dive).", { + batch_id: { type: "string", required: true }, + command_id: { type: "string", required: true } + }, getBizCollectCommandRaw, getClient, t), tool2("netx__getTopologyTree", "Get topology folder tree (nav roots + Root map canvases). Start here before createTopologyFolder.", { compact: bool4(), max_depth: num4() }, getTopologyTree, getClient, t), tool2("netx__getTopologyView", "Get a topology view by view_id. Default detail=summary with sample_nodes + links[].", { view_id: { type: "string", required: true }, diff --git a/lib/agent-tools.js b/lib/agent-tools.js index d79da17..ba073e2 100644 --- a/lib/agent-tools.js +++ b/lib/agent-tools.js @@ -1,7 +1,8 @@ // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), - topology: Object.freeze({ inPreset: false, public: false }) + topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var TOOLS_BY_GROUP = Object.freeze({ ops: Object.freeze([ @@ -37,15 +38,25 @@ var TOOLS_BY_GROUP = Object.freeze({ "netx__suggestSinkHubs", "netx__analyzeTopologyViewLayout", "netx__sinkTopologyDualUnits" + ]), + bizMonitor: Object.freeze([ + "netx__getBizMonitorContext", + "netx__getBizMonitorBoard", + "netx__listBizMonitorReds", + "netx__getBizMonitorDiffs", + "netx__getBizCollectBatch", + "netx__getBizCollectCommandRaw" ]) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", - topology: "topology" + topology: "topology", + bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", - "topology" + "topology", + "bizMonitor" ]); function groupsForPlane(groups, plane, only) { const policy = groups ?? DEFAULT_CAPABILITY_GROUPS; @@ -60,12 +71,12 @@ function groupsForced(only) { return CAPABILITY_GROUP_IDS.filter((id) => normalized.includes(id)); } function toolNamesForGroups(groupIds) { - const names = new Set; + const out = new Set; for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) - names.add(tool); + for (const name of TOOLS_BY_GROUP[id] ?? []) + out.add(name); } - return names; + return out; } // src/netx/group-skills.ts @@ -1458,6 +1469,68 @@ async function findTopologyPaths(client, args, signal) { body.to_managed_ne_id = toMid; return client.post("/v1/topology/fabric/paths", body, signal, 30000); } +async function getBizMonitorContext(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + const taskId = str2(args, "task_id").trim(); + if (!projectId && !taskId) { + return { ok: false, error: "project_id_or_task_id_required" }; + } + const params = {}; + if (projectId) + params.project_id = projectId; + if (taskId) + params.task_id = taskId; + return client.get("/v1/biz-migration/monitor-context", params, signal); +} +async function getBizMonitorBoard(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + const params = {}; + const runId = str2(args, "run_id").trim(); + if (runId) + params.run_id = runId; + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal); +} +async function listBizMonitorReds(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + if (!projectId) + return { ok: false, error: "project_id_required" }; + const params = {}; + putStr(params, args, ["status"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + return client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, params, signal); +} +async function getBizMonitorDiffs(client, args, signal) { + const runId = str2(args, "run_id").trim(); + if (!runId) + return { ok: false, error: "run_id_required" }; + const params = {}; + putStr(params, args, ["metric_id", "sheet_id", "verdict", "color", "q"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + const offset = num2(args, "offset"); + if (offset !== undefined) + params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER); + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal); +} +async function getBizCollectBatch(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal); +} +async function getBizCollectCommandRaw(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + const commandId = str2(args, "command_id").trim(); + if (!batchId || !commandId) { + return { ok: false, error: "batch_id_and_command_id_required" }; + } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, {}, signal); +} // src/netx/topology-handlers.ts function asRecord2(value) { @@ -2282,6 +2355,36 @@ function registerNetxTools(ctx, connection, options) { layer: str4(), detail: str4("summary | full") }, findTopologyPaths, getClient, t), + tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.", { + project_id: str4("Cutover / biz-migration project id"), + task_id: str4("biz_state task id (optional if project_id set)") + }, getBizMonitorContext, getClient, t), + tool2("netx__getBizMonitorBoard", "Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.", { + batch_id: { type: "string", required: true }, + run_id: str4("Optional evaluate run id") + }, getBizMonitorBoard, getClient, t), + tool2("netx__listBizMonitorReds", "Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).", { + project_id: { type: "string", required: true }, + status: str4("open|carried|resolved or empty=all"), + limit: num4() + }, listBizMonitorReds, getClient, t), + tool2("netx__getBizMonitorDiffs", "Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.", { + run_id: { type: "string", required: true }, + metric_id: str4(), + sheet_id: str4(), + verdict: str4(), + color: str4("e.g. red"), + q: str4(), + limit: num4(), + offset: num4() + }, getBizMonitorDiffs, getClient, t), + tool2("netx__getBizCollectBatch", "One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.", { + batch_id: { type: "string", required: true } + }, getBizCollectBatch, getClient, t), + tool2("netx__getBizCollectCommandRaw", "Full CLI raw_text for one collect command inside a batch (deep dive).", { + batch_id: { type: "string", required: true }, + command_id: { type: "string", required: true } + }, getBizCollectCommandRaw, getClient, t), tool2("netx__getTopologyTree", "Get topology folder tree (nav roots + Root map canvases). Start here before createTopologyFolder.", { compact: bool4(), max_depth: num4() }, getTopologyTree, getClient, t), tool2("netx__getTopologyView", "Get a topology view by view_id. Default detail=summary with sample_nodes + links[].", { view_id: { type: "string", required: true }, diff --git a/lib/client.js b/lib/client.js index 436e37b..346f59a 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1451,6 +1451,20 @@ function NetxopsCard(props) { onEditPublic: (checked) => { props.edit("groupTopologyPublic", checked ? "true" : "false"); } + }), + /* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, { + title: t("groupBizMonitor"), + inPresetLabel: t("groupInPreset"), + publicLabel: t("groupPublic"), + inPreset: state.groupBizMonitorInPreset, + published: state.groupBizMonitorPublic, + disabled, + onEditInPreset: (checked) => { + props.edit("groupBizMonitorInPreset", checked ? "true" : "false"); + }, + onEditPublic: (checked) => { + props.edit("groupBizMonitorPublic", checked ? "true" : "false"); + } }) ] }), @@ -1995,6 +2009,8 @@ class NetxopsCardController { booleanField("groupOpsPublic"), booleanField("groupTopologyInPreset"), booleanField("groupTopologyPublic"), + booleanFieldPersistFalse("groupBizMonitorInPreset"), + booleanField("groupBizMonitorPublic"), booleanFieldPersistFalse("groupKbInPreset"), booleanField("groupKbPublic"), booleanField("alarmPushEnabled"), @@ -2312,6 +2328,8 @@ class NetxopsCardController { groupOpsPublic: this.form.field("groupOpsPublic"), groupTopologyInPreset: this.form.field("groupTopologyInPreset"), groupTopologyPublic: this.form.field("groupTopologyPublic"), + groupBizMonitorInPreset: this.form.field("groupBizMonitorInPreset"), + groupBizMonitorPublic: this.form.field("groupBizMonitorPublic"), groupKbInPreset: this.form.field("groupKbInPreset"), groupKbPublic: this.form.field("groupKbPublic"), alarmPushEnabled: this.form.field("alarmPushEnabled"), @@ -2471,6 +2489,7 @@ var en = { nmsProviderHint: "zte-ume", groupOps: "ops", groupTopology: "topology", + groupBizMonitor: "bizMonitor", groupKb: "Knowledge skills", groupInPreset: "In Netx Ops preset", groupPublic: "Publish to other presets", @@ -2548,6 +2567,7 @@ var zh = { nmsProviderHint: "zte-ume", groupOps: "ops", groupTopology: "topology", + groupBizMonitor: "bizMonitor", groupKb: "知识库技能", groupInPreset: "在 Netx Ops 预设中启用", groupPublic: "对其他预设公开", diff --git a/lib/index.js b/lib/index.js index c3e2de1..6415f41 100644 --- a/lib/index.js +++ b/lib/index.js @@ -572,7 +572,8 @@ async function dispatchAlarmToSinks(ctx, payload, options, sinks = {}) { // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), - topology: Object.freeze({ inPreset: false, public: false }) + topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var TOOLS_BY_GROUP = Object.freeze({ ops: Object.freeze([ @@ -608,15 +609,25 @@ var TOOLS_BY_GROUP = Object.freeze({ "netx__suggestSinkHubs", "netx__analyzeTopologyViewLayout", "netx__sinkTopologyDualUnits" + ]), + bizMonitor: Object.freeze([ + "netx__getBizMonitorContext", + "netx__getBizMonitorBoard", + "netx__listBizMonitorReds", + "netx__getBizMonitorDiffs", + "netx__getBizCollectBatch", + "netx__getBizCollectCommandRaw" ]) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", - topology: "topology" + topology: "topology", + bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", - "topology" + "topology", + "bizMonitor" ]); function capabilityGroupsFromSettings(fields) { const src = fields ?? {}; @@ -633,6 +644,10 @@ function capabilityGroupsFromSettings(fields) { opsInPreset = legacy.some((v) => v === true); } } + let bizMonitorInPreset = true; + if (src.groupBizMonitorInPreset !== undefined) { + bizMonitorInPreset = src.groupBizMonitorInPreset !== false; + } return { ops: { inPreset: opsInPreset, @@ -641,6 +656,10 @@ function capabilityGroupsFromSettings(fields) { topology: { inPreset: src.groupTopologyInPreset === true || src.groupTopologyLayoutInPreset === true, public: src.groupTopologyPublic === true || src.groupTopologyLayoutPublic === true + }, + bizMonitor: { + inPreset: bizMonitorInPreset, + public: src.groupBizMonitorPublic === true } }; } @@ -653,12 +672,12 @@ function groupsForPlane(groups, plane, only) { return enabled.filter((id) => allow.has(id)); } function toolNamesForGroups(groupIds) { - const names = new Set; + const out = new Set; for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) - names.add(tool); + for (const name of TOOLS_BY_GROUP[id] ?? []) + out.add(name); } - return names; + return out; } // src/netx/group-skills.ts @@ -3709,6 +3728,68 @@ async function findTopologyPaths(client, args, signal) { body.to_managed_ne_id = toMid; return client.post("/v1/topology/fabric/paths", body, signal, 30000); } +async function getBizMonitorContext(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + const taskId = str2(args, "task_id").trim(); + if (!projectId && !taskId) { + return { ok: false, error: "project_id_or_task_id_required" }; + } + const params = {}; + if (projectId) + params.project_id = projectId; + if (taskId) + params.task_id = taskId; + return client.get("/v1/biz-migration/monitor-context", params, signal); +} +async function getBizMonitorBoard(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + const params = {}; + const runId = str2(args, "run_id").trim(); + if (runId) + params.run_id = runId; + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal); +} +async function listBizMonitorReds(client, args, signal) { + const projectId = str2(args, "project_id").trim(); + if (!projectId) + return { ok: false, error: "project_id_required" }; + const params = {}; + putStr(params, args, ["status"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + return client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, params, signal); +} +async function getBizMonitorDiffs(client, args, signal) { + const runId = str2(args, "run_id").trim(); + if (!runId) + return { ok: false, error: "run_id_required" }; + const params = {}; + putStr(params, args, ["metric_id", "sheet_id", "verdict", "color", "q"]); + const limit = num2(args, "limit"); + if (limit !== undefined) + params.limit = clampInt(limit, 200, 1, 500); + const offset = num2(args, "offset"); + if (offset !== undefined) + params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER); + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal); +} +async function getBizCollectBatch(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + if (!batchId) + return { ok: false, error: "batch_id_required" }; + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal); +} +async function getBizCollectCommandRaw(client, args, signal) { + const batchId = str2(args, "batch_id").trim(); + const commandId = str2(args, "command_id").trim(); + if (!batchId || !commandId) { + return { ok: false, error: "batch_id_and_command_id_required" }; + } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, {}, signal); +} // src/netx/topology-handlers.ts function asRecord2(value) { @@ -4533,6 +4614,36 @@ function registerNetxTools(ctx, connection, options) { layer: str4(), detail: str4("summary | full") }, findTopologyPaths, getClient, t), + tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.", { + project_id: str4("Cutover / biz-migration project id"), + task_id: str4("biz_state task id (optional if project_id set)") + }, getBizMonitorContext, getClient, t), + tool2("netx__getBizMonitorBoard", "Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.", { + batch_id: { type: "string", required: true }, + run_id: str4("Optional evaluate run id") + }, getBizMonitorBoard, getClient, t), + tool2("netx__listBizMonitorReds", "Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).", { + project_id: { type: "string", required: true }, + status: str4("open|carried|resolved or empty=all"), + limit: num4() + }, listBizMonitorReds, getClient, t), + tool2("netx__getBizMonitorDiffs", "Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.", { + run_id: { type: "string", required: true }, + metric_id: str4(), + sheet_id: str4(), + verdict: str4(), + color: str4("e.g. red"), + q: str4(), + limit: num4(), + offset: num4() + }, getBizMonitorDiffs, getClient, t), + tool2("netx__getBizCollectBatch", "One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.", { + batch_id: { type: "string", required: true } + }, getBizCollectBatch, getClient, t), + tool2("netx__getBizCollectCommandRaw", "Full CLI raw_text for one collect command inside a batch (deep dive).", { + batch_id: { type: "string", required: true }, + command_id: { type: "string", required: true } + }, getBizCollectCommandRaw, getClient, t), tool2("netx__getTopologyTree", "Get topology folder tree (nav roots + Root map canvases). Start here before createTopologyFolder.", { compact: bool4(), max_depth: num4() }, getTopologyTree, getClient, t), tool2("netx__getTopologyView", "Get a topology view by view_id. Default detail=summary with sample_nodes + links[].", { view_id: { type: "string", required: true }, @@ -4734,6 +4845,8 @@ var Config = z.object({ groupOpsPublic: z.boolean().default(false), groupTopologyInPreset: z.boolean().default(false), groupTopologyPublic: z.boolean().default(false), + groupBizMonitorInPreset: z.boolean().default(true), + groupBizMonitorPublic: z.boolean().default(false), kbRoot: z.string().default(""), groupKbInPreset: z.boolean().default(true), groupKbPublic: z.boolean().default(false) diff --git a/package.json b/package.json index b2b53c8..7bbc103 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dsh-netxops", - "version": "0.1.39", + "version": "0.1.40", "description": "DeepSeek Harness Netx Ops: ops/topology + IM delivery + session export + knowledge-base MANIFEST/_skills + localSkills", "license": "MIT", "type": "module", @@ -13,6 +13,7 @@ "./tools-nms": "./lib/agent-tools-nms.js", "./tools-common": "./lib/agent-tools-common.js", "./tools-topology": "./lib/agent-tools-topology.js", + "./tools-biz-monitor": "./lib/agent-tools-biz-monitor.js", "./client": "./lib/client.js", "./package.json": "./package.json" }, @@ -24,6 +25,7 @@ "lib/agent-tools-nms.js", "lib/agent-tools-common.js", "lib/agent-tools-topology.js", + "lib/agent-tools-biz-monitor.js", "lib/client.js", "presets/", "cordis.patch.yml", diff --git a/presets/netxops/skills/README.md b/presets/netxops/skills/README.md index 83d5b5e..8e52795 100644 --- a/presets/netxops/skills/README.md +++ b/presets/netxops/skills/README.md @@ -1,14 +1,14 @@ -# Mirrored from netx/skills — do not long-edit here +# Mirrored from netx/skills 鈥?do not long-edit here Canonical: `D:\project\chatgpt\netx\skills` -```powershell +`powershell powershell -File .\scripts\sync-skills-from-netx.ps1 -``` +` Runtime prefers `NETX_SKILLS_ROOT` or sibling `../netx/skills`. -Groups: **ops** (`netx-ops`) + **topology** (`netx-topology`). One group ↔ one skill. +Groups: **ops** (`netx-ops`) + **topology** (`netx-topology`). One group 鈫?one skill. **Keep netx-ops pure netx** (alarms / inventory / CLI). Knowledge-base triage belongs in -operator-subset `_skills/kb-*` packs registered by the plugin — do **not** write KB linkage +operator-subset `_skills/kb-*` packs registered by the plugin 鈥?do **not** write KB linkage back into `netx-ops`. diff --git a/presets/netxops/skills/biz-monitor/netx-biz-monitor/SKILL.md b/presets/netxops/skills/biz-monitor/netx-biz-monitor/SKILL.md new file mode 100644 index 0000000..76dd1fd --- /dev/null +++ b/presets/netxops/skills/biz-monitor/netx-biz-monitor/SKILL.md @@ -0,0 +1,61 @@ +--- +name: netx-biz-monitor +description: >- + Overnight cutover / biz_state monitor analysis via netxops host tools + (netx__getBizMonitor*). Given a project_id or task_id, read definitions, + board, reds/diffs with evidence (device-raw A/B, show commands), and judge + tool false-positive vs real business fault. Does not create templates/tasks. +--- + +# netx-biz-monitor(割接 / 业务监控只读分析) + +人配好监控与任务;AI 当晚用 **project_id**(或 task_id)做归因。 +工具在 **netxops**(`netx__*`),直连 netx REST,**不是** netx-mcp。 + +## 工具(能力组 `bizMonitor`) + +| 工具 | 用途 | +|------|------| +| `netx__getBizMonitorContext` | 任务定义:项目、监控/对比模板、归一化规则、端口映射、关联任务与命令项 | +| `netx__getBizMonitorBoard` | 批次进度、evaluate run、sheet 卡、verdict 统计 | +| `netx__listBizMonitorReds` | 红单 + **evidence** | +| `netx__getBizMonitorDiffs` | run 明细(可滤 color=red) | +| `netx__getBizCollectBatch` | 某次采集:commands / metrics | +| `netx__getBizCollectCommandRaw` | 单命令全量 show 输出 | + +交叉验证真障时再用 ops:`netx__execManagedNe` / 拓扑路径。 + +## 关键约定(必守) + +- **`old_key` / `new_key`**:设备采集原文 **A/B**(大屏与结论用这个) +- **`match_*` / evidence.iface.normalized|map_***:内部配对,**不要**把映射后的 BB 说成两侧端口 +- **evidence.*.command.raw_command**:实际 show 命令 +- **evidence.*.device / collect**:哪台设备、何时采的 + +## 推荐顺序 + +1. `getBizMonitorContext(project_id)` — 弄清盯什么 +2. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped +3. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)` +4. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤? +5. 需要原文 → `getBizCollectCommandRaw` +6. 像真障 → `execManagedNe` 现场核对 + +## Verdict 速查(skill 解释,工具不硬编码) + +| verdict | 含义(窗口内) | +|---------|----------------| +| migrated | expect 项已迁到新侧 | +| anomaly | 异常(多为红) | +| unfinished | expect 未完成(验收常红) | +| migrating / expected / ok | 过程态 / 正常 | +| lost / not_involved | 丢失或无关 | + +## 误报优先查 + +1. `parse_status` 非 ok / 行数为 0 +2. board:`current_missing` / `collect_skipped` +3. 端口映射漏配、归一化前后不一致导致假 anomaly +4. 再谈真实业务问题 + +配置整改与关红单由人做;本 skill **只读分析**。 diff --git a/scripts/build-host.mjs b/scripts/build-host.mjs index c4ab112..bd30554 100644 --- a/scripts/build-host.mjs +++ b/scripts/build-host.mjs @@ -34,6 +34,7 @@ const entries = [ { entry: join(root, 'src/agent-tools-nms.ts'), out: join(outDir, 'agent-tools-nms.js') }, { entry: join(root, 'src/agent-tools-common.ts'), out: join(outDir, 'agent-tools-common.js') }, { entry: join(root, 'src/agent-tools-topology.ts'), out: join(outDir, 'agent-tools-topology.js') }, + { entry: join(root, 'src/agent-tools-biz-monitor.ts'), out: join(outDir, 'agent-tools-biz-monitor.js') }, ] mkdirSync(outDir, { recursive: true }) diff --git a/scripts/sync-skills-from-netx.ps1 b/scripts/sync-skills-from-netx.ps1 index c979546..f9d0142 100644 --- a/scripts/sync-skills-from-netx.ps1 +++ b/scripts/sync-skills-from-netx.ps1 @@ -17,7 +17,7 @@ $src = Join-Path $NetxRoot "skills" $dst = Join-Path $opsRoot "presets\netxops\skills" if (-not (Test-Path $src)) { throw "missing $src" } -foreach ($group in @("ops", "topology")) { +foreach ($group in @("ops", "topology", "biz-monitor")) { $from = Join-Path $src $group $to = Join-Path $dst $group if (-not (Test-Path $from)) { throw "missing group $from" } diff --git a/src/agent-tools-biz-monitor.ts b/src/agent-tools-biz-monitor.ts new file mode 100644 index 0000000..3040b19 --- /dev/null +++ b/src/agent-tools-biz-monitor.ts @@ -0,0 +1,15 @@ +/** + * Forced bizMonitor-group tools + skill (cutover / biz_state read analysis). + * + * @module dsh-netxops/tools-biz-monitor + */ + +import type { Context } from '@deepseek-ai/cordis' +import { applyGroupToolsPlugin } from './netx/group-tools-plugin.ts' + +export const name = 'netxops-tools-biz-monitor' +export const inject = ['tools'] + +export function apply(ctx: Context): void { + applyGroupToolsPlugin(ctx, { name, mode: 'forced', only: ['bizMonitor'] }) +} diff --git a/src/client/NetxopsCard.tsx b/src/client/NetxopsCard.tsx index 7943898..6201df3 100644 --- a/src/client/NetxopsCard.tsx +++ b/src/client/NetxopsCard.tsx @@ -564,6 +564,20 @@ export function NetxopsCard(props: NetxopsCardProps) { props.edit('groupTopologyPublic', checked ? 'true' : 'false') }} /> + { + props.edit('groupBizMonitorInPreset', checked ? 'true' : 'false') + }} + onEditPublic={(checked) => { + props.edit('groupBizMonitorPublic', checked ? 'true' : 'false') + }} + />
diff --git a/src/client/controller.ts b/src/client/controller.ts index edb9ad3..da93e4b 100644 --- a/src/client/controller.ts +++ b/src/client/controller.ts @@ -57,6 +57,8 @@ export interface NetxopsSettings { groupOpsPublic?: boolean groupTopologyInPreset?: boolean groupTopologyPublic?: boolean + groupBizMonitorInPreset?: boolean + groupBizMonitorPublic?: boolean nmsProvider?: string /** Operator-subset knowledge package root (Host-local path). */ kbRoot?: string @@ -82,6 +84,8 @@ export interface NetxopsCardState extends CardShell { groupOpsPublic: CardFieldState groupTopologyInPreset: CardFieldState groupTopologyPublic: CardFieldState + groupBizMonitorInPreset: CardFieldState + groupBizMonitorPublic: CardFieldState nmsProvider: CardFieldState kbRoot: CardFieldState groupKbInPreset: CardFieldState @@ -187,6 +191,8 @@ export class NetxopsCardController { booleanField('groupOpsPublic'), booleanField('groupTopologyInPreset'), booleanField('groupTopologyPublic'), + booleanFieldPersistFalse('groupBizMonitorInPreset'), + booleanField('groupBizMonitorPublic'), booleanFieldPersistFalse('groupKbInPreset'), booleanField('groupKbPublic'), booleanField('alarmPushEnabled'), @@ -599,6 +605,8 @@ export class NetxopsCardController { groupOpsPublic: this.form.field('groupOpsPublic'), groupTopologyInPreset: this.form.field('groupTopologyInPreset'), groupTopologyPublic: this.form.field('groupTopologyPublic'), + groupBizMonitorInPreset: this.form.field('groupBizMonitorInPreset'), + groupBizMonitorPublic: this.form.field('groupBizMonitorPublic'), groupKbInPreset: this.form.field('groupKbInPreset'), groupKbPublic: this.form.field('groupKbPublic'), alarmPushEnabled: this.form.field('alarmPushEnabled'), diff --git a/src/client/locales.ts b/src/client/locales.ts index 472c6e7..154daf1 100644 --- a/src/client/locales.ts +++ b/src/client/locales.ts @@ -21,6 +21,7 @@ export type NetxopsLocaleKey = | 'nmsProviderHint' | 'groupOps' | 'groupTopology' + | 'groupBizMonitor' | 'groupKb' | 'groupInPreset' | 'groupPublic' @@ -98,6 +99,7 @@ export const en: Record = { nmsProviderHint: 'zte-ume', groupOps: 'ops', groupTopology: 'topology', + groupBizMonitor: 'bizMonitor', groupKb: 'Knowledge skills', groupInPreset: 'In Netx Ops preset', groupPublic: 'Publish to other presets', @@ -176,6 +178,7 @@ export const zh: Record = { nmsProviderHint: 'zte-ume', groupOps: 'ops', groupTopology: 'topology', + groupBizMonitor: 'bizMonitor', groupKb: '知识库技能', groupInPreset: '在 Netx Ops 预设中启用', groupPublic: '对其他预设公开', diff --git a/src/index.ts b/src/index.ts index e7896ab..475e92f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -152,6 +152,10 @@ export interface Config { groupTopologyInPreset: boolean /** Publish topology tools/skills to other presets (default off). */ groupTopologyPublic: boolean + /** biz-monitor (cutover / biz_state read) tools in the Netx Ops preset (default on). */ + groupBizMonitorInPreset: boolean + /** Publish biz-monitor tools/skills to other presets (default off). */ + groupBizMonitorPublic: boolean /** * Absolute (or Host-local) path to an operator-subset knowledge package. * Empty = unconfigured. Plugin locates `MANIFEST.json` (≤3 levels) and @@ -183,6 +187,8 @@ export const Config: z = z.object({ groupOpsPublic: z.boolean().default(false), groupTopologyInPreset: z.boolean().default(false), groupTopologyPublic: z.boolean().default(false), + groupBizMonitorInPreset: z.boolean().default(true), + groupBizMonitorPublic: z.boolean().default(false), kbRoot: z.string().default(''), groupKbInPreset: z.boolean().default(true), groupKbPublic: z.boolean().default(false), diff --git a/src/netx/capability-groups.ts b/src/netx/capability-groups.ts index 2c85ed2..faa15f6 100644 --- a/src/netx/capability-groups.ts +++ b/src/netx/capability-groups.ts @@ -5,10 +5,11 @@ * * - `ops` → skill `netx-ops` — NMS alarms/inventory/SQL + managed CLI + findTopologyPaths * - `topology` → skill `netx-topology` — canvas / fabric / dual_unit / layout + * - `bizMonitor` → skill `netx-biz-monitor` — cutover / biz_state read + overnight analysis */ /** Stable group ids used in settings, skill dirs, and registration filters. */ -export type NetxCapabilityGroupId = 'ops' | 'topology' +export type NetxCapabilityGroupId = 'ops' | 'topology' | 'bizMonitor' /** Per-group exposure knobs. */ export interface NetxGroupExposure { @@ -27,14 +28,17 @@ export interface NetxCapabilityGroupSettingsFields { groupOpsPublic: boolean groupTopologyInPreset: boolean groupTopologyPublic: boolean + groupBizMonitorInPreset: boolean + groupBizMonitorPublic: boolean } /** - * Default: ops in Ops preset; topology and all public off. + * Default: ops in Ops preset; topology / bizMonitor and all public off. */ export const DEFAULT_CAPABILITY_GROUPS: NetxCapabilityGroups = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), topology: Object.freeze({ inPreset: false, public: false }), + bizMonitor: Object.freeze({ inPreset: true, public: false }), }) /** @@ -76,17 +80,27 @@ export const TOOLS_BY_GROUP: Readonly/`. */ export const SKILL_DIR_BY_GROUP: Readonly> = Object.freeze({ ops: 'ops', topology: 'topology', + bizMonitor: 'biz-monitor', }) export const CAPABILITY_GROUP_IDS: readonly NetxCapabilityGroupId[] = Object.freeze([ 'ops', 'topology', + 'bizMonitor', ]) /** @@ -126,6 +140,11 @@ export function capabilityGroupsFromSettings( } } + let bizMonitorInPreset = true + if (src.groupBizMonitorInPreset !== undefined) { + bizMonitorInPreset = src.groupBizMonitorInPreset !== false + } + return { ops: { inPreset: opsInPreset, @@ -140,6 +159,10 @@ export function capabilityGroupsFromSettings( public: src.groupTopologyPublic === true || src.groupTopologyLayoutPublic === true, }, + bizMonitor: { + inPreset: bizMonitorInPreset, + public: src.groupBizMonitorPublic === true, + }, } } @@ -177,9 +200,9 @@ export function groupsForced( * Tool name allow-list for the given groups. */ export function toolNamesForGroups(groupIds: readonly NetxCapabilityGroupId[]): Set { - const names = new Set() + const out = new Set() for (const id of groupIds) { - for (const tool of TOOLS_BY_GROUP[id]) names.add(tool) + for (const name of TOOLS_BY_GROUP[id] ?? []) out.add(name) } - return names + return out } diff --git a/src/netx/handlers.ts b/src/netx/handlers.ts index c8f4108..aeca0cd 100644 --- a/src/netx/handlers.ts +++ b/src/netx/handlers.ts @@ -330,3 +330,101 @@ export async function findTopologyPaths(client: NetxClient, args: NetxJson, sign else body.to_managed_ne_id = toMid return client.post('/v1/topology/fabric/paths', body, signal, 30_000) } + +// ── bizMonitor group (cutover / biz_state read) ───────────────────────────── + +/** Fat cutover / biz_state definition bundle (templates, mapping, tasks). */ +export async function getBizMonitorContext( + client: NetxClient, + args: NetxJson, + signal?: AbortSignal, +): Promise { + const projectId = str(args, 'project_id').trim() + const taskId = str(args, 'task_id').trim() + if (!projectId && !taskId) { + return { ok: false, error: 'project_id_or_task_id_required' } + } + const params: Record = {} + if (projectId) params.project_id = projectId + if (taskId) params.task_id = taskId + return client.get('/v1/biz-migration/monitor-context', params, signal) +} + +/** Cutover batch board + latest evaluate run summary. */ +export async function getBizMonitorBoard( + client: NetxClient, + args: NetxJson, + signal?: AbortSignal, +): Promise { + const batchId = str(args, 'batch_id').trim() + if (!batchId) return { ok: false, error: 'batch_id_required' } + const params: Record = {} + const runId = str(args, 'run_id').trim() + if (runId) params.run_id = runId + return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal) +} + +/** Red tickets with evidence (raw A/B + show command). */ +export async function listBizMonitorReds( + client: NetxClient, + args: NetxJson, + signal?: AbortSignal, +): Promise { + const projectId = str(args, 'project_id').trim() + if (!projectId) return { ok: false, error: 'project_id_required' } + const params: Record = {} + putStr(params, args, ['status']) + const limit = num(args, 'limit') + if (limit !== undefined) params.limit = clampInt(limit, 200, 1, 500) + return client.get( + `/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`, + params, + signal, + ) +} + +/** Paged evaluate diffs (old_key/new_key = device-raw A/B). */ +export async function getBizMonitorDiffs( + client: NetxClient, + args: NetxJson, + signal?: AbortSignal, +): Promise { + const runId = str(args, 'run_id').trim() + if (!runId) return { ok: false, error: 'run_id_required' } + const params: Record = {} + putStr(params, args, ['metric_id', 'sheet_id', 'verdict', 'color', 'q']) + const limit = num(args, 'limit') + if (limit !== undefined) params.limit = clampInt(limit, 200, 1, 500) + const offset = num(args, 'offset') + if (offset !== undefined) params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER) + return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal) +} + +/** One biz_state collect batch (commands + metrics). */ +export async function getBizCollectBatch( + client: NetxClient, + args: NetxJson, + signal?: AbortSignal, +): Promise { + const batchId = str(args, 'batch_id').trim() + if (!batchId) return { ok: false, error: 'batch_id_required' } + return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal) +} + +/** Full CLI raw_text for one collect command. */ +export async function getBizCollectCommandRaw( + client: NetxClient, + args: NetxJson, + signal?: AbortSignal, +): Promise { + const batchId = str(args, 'batch_id').trim() + const commandId = str(args, 'command_id').trim() + if (!batchId || !commandId) { + return { ok: false, error: 'batch_id_and_command_id_required' } + } + return client.get( + `/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`, + {}, + signal, + ) +} diff --git a/src/netx/tools.ts b/src/netx/tools.ts index fb255be..d4e4f7e 100644 --- a/src/netx/tools.ts +++ b/src/netx/tools.ts @@ -316,6 +316,67 @@ export function registerNetxTools( }, H.findTopologyPaths, getClient, t, ), + // ── bizMonitor group (cutover / biz_state read via netx REST) ───────── + tool( + 'netx__getBizMonitorContext', + 'Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.', + { + project_id: str('Cutover / biz-migration project id'), + task_id: str('biz_state task id (optional if project_id set)'), + }, + H.getBizMonitorContext, getClient, t, + ), + tool( + 'netx__getBizMonitorBoard', + 'Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.', + { + batch_id: { type: 'string' as const, required: true as const }, + run_id: str('Optional evaluate run id'), + }, + H.getBizMonitorBoard, getClient, t, + ), + tool( + 'netx__listBizMonitorReds', + 'Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).', + { + project_id: { type: 'string' as const, required: true as const }, + status: str('open|carried|resolved or empty=all'), + limit: num(), + }, + H.listBizMonitorReds, getClient, t, + ), + tool( + 'netx__getBizMonitorDiffs', + 'Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.', + { + run_id: { type: 'string' as const, required: true as const }, + metric_id: str(), + sheet_id: str(), + verdict: str(), + color: str('e.g. red'), + q: str(), + limit: num(), + offset: num(), + }, + H.getBizMonitorDiffs, getClient, t, + ), + tool( + 'netx__getBizCollectBatch', + 'One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.', + { + batch_id: { type: 'string' as const, required: true as const }, + }, + H.getBizCollectBatch, getClient, t, + ), + tool( + 'netx__getBizCollectCommandRaw', + 'Full CLI raw_text for one collect command inside a batch (deep dive).', + { + batch_id: { type: 'string' as const, required: true as const }, + command_id: { type: 'string' as const, required: true as const }, + }, + H.getBizCollectCommandRaw, getClient, t, + ), // ── topology group (netx-topology canvas / fabric) ───────────────────── tool( 'netx__getTopologyTree', diff --git a/test/capability-groups.test.mjs b/test/capability-groups.test.mjs index f2954cf..8cf37c3 100644 --- a/test/capability-groups.test.mjs +++ b/test/capability-groups.test.mjs @@ -15,19 +15,21 @@ import { TOOLS_BY_GROUP, } from '../src/netx/capability-groups.ts' -test('defaults: ops in preset; topology and public off', () => { +test('defaults: ops + bizMonitor in preset; topology and public off', () => { assert.deepEqual(DEFAULT_CAPABILITY_GROUPS, { ops: { inPreset: true, public: false }, topology: { inPreset: false, public: false }, + bizMonitor: { inPreset: true, public: false }, }) - assert.deepEqual(groupsForPlane(undefined, 'preset'), ['ops']) + assert.deepEqual(groupsForPlane(undefined, 'preset'), ['ops', 'bizMonitor']) assert.deepEqual(groupsForPlane(undefined, 'public'), []) }) -test('one group one skill dir; ops owns NMS + managed CLI; topology owns canvas', () => { +test('one group one skill dir; ops owns NMS + managed CLI; topology owns canvas; bizMonitor owns cutover read', () => { assert.deepEqual(SKILL_DIR_BY_GROUP, { ops: 'ops', topology: 'topology', + bizMonitor: 'biz-monitor', }) assert.ok(TOOLS_BY_GROUP.ops.includes('netx__findTopologyPaths')) assert.ok(TOOLS_BY_GROUP.ops.includes('netx__execManagedNe')) @@ -36,11 +38,14 @@ test('one group one skill dir; ops owns NMS + managed CLI; topology owns canvas' assert.ok(TOOLS_BY_GROUP.topology.includes('netx__layoutTopologyView')) assert.ok(TOOLS_BY_GROUP.topology.includes('netx__suggestSinkHubs')) assert.ok(TOOLS_BY_GROUP.topology.includes('netx__sinkTopologyDualUnits')) + assert.ok(TOOLS_BY_GROUP.bizMonitor.includes('netx__getBizMonitorContext')) + assert.ok(TOOLS_BY_GROUP.bizMonitor.includes('netx__getBizCollectCommandRaw')) }) test('capabilityGroupsFromSettings honors explicit false for inPreset defaults', () => { const groups = capabilityGroupsFromSettings({ groupOpsInPreset: false, + groupBizMonitorInPreset: false, groupTopologyInPreset: true, groupTopologyPublic: true, }) @@ -49,6 +54,7 @@ test('capabilityGroupsFromSettings honors explicit false for inPreset defaults', assert.ok(toolNamesForGroups(['topology']).has('netx__layoutTopologyView')) assert.ok(toolNamesForGroups(['topology']).has('netx__sinkTopologyDualUnits')) assert.equal(toolNamesForGroups(['topology']).has('netx__queryNmsAlarms'), false) + assert.equal(toolNamesForGroups(['topology']).has('netx__getBizMonitorContext'), false) }) test('legacy groupNms/common/managedNe map to ops; legacy layout flags OR into topology', () => { @@ -61,6 +67,7 @@ test('legacy groupNms/common/managedNe map to ops; legacy layout flags OR into t assert.equal(groups.ops.inPreset, false) assert.equal(groups.ops.public, true) assert.equal(groups.topology.inPreset, true) + assert.equal(groups.bizMonitor.inPreset, true) }) test('legacy any-true turns ops inPreset on', () => { @@ -74,4 +81,5 @@ test('legacy any-true turns ops inPreset on', () => { test('groupsForced ignores settings for per-export mounts', () => { assert.deepEqual(groupsForced(['ops']), ['ops']) assert.deepEqual(groupsForced(['ops', 'topology']), ['ops', 'topology']) + assert.deepEqual(groupsForced(['bizMonitor']), ['bizMonitor']) })