mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-08 23:50:48 +08:00
Add listBizMonitorBatches so agents can discover cutover/collect batch and run ids.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c1d07f7509
commit
b235957c34
12 changed files with 1518 additions and 26 deletions
|
|
@ -28,6 +28,7 @@ Host tools call netx REST directly (**not** netx-mcp). Token needs `biz-monitor:
|
|||
| Tool | Role |
|
||||
|------|------|
|
||||
| `listBizMonitors` | Catalog: cutover projects + biz_state tasks (no id required) |
|
||||
| `listBizMonitorBatches` | Cutover / collect batch ids (+ optional evaluate runs) |
|
||||
| `getBizMonitorContext` | Project + templates + normalize + port map + tasks |
|
||||
| `getBizMonitorBoard` | Batch / evaluate progress |
|
||||
| `listBizMonitorReds` / `getBizMonitorDiffs` | Reds & diffs with evidence (raw A/B + show cmd) |
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -1576,7 +1577,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2466,13 +2636,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -1576,7 +1577,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2466,13 +2636,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -1576,7 +1577,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2466,13 +2636,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -1576,7 +1577,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2466,13 +2636,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -1576,7 +1577,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2466,13 +2636,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -1576,7 +1577,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2466,13 +2636,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
184
lib/index.js
184
lib/index.js
|
|
@ -612,6 +612,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
|
|
@ -3835,7 +3836,176 @@ async function listBizMonitors(client, args, signal) {
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0
|
||||
},
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle."
|
||||
next: "Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id."
|
||||
}
|
||||
};
|
||||
}
|
||||
function slimCutoverBatch(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function slimCollectBatch(row, taskId = "") {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at
|
||||
};
|
||||
}
|
||||
function slimEvalRun(row) {
|
||||
const summary = asRecord(row.summary);
|
||||
const progress = asRecord(summary.progress);
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total
|
||||
};
|
||||
}
|
||||
async function listBizMonitorBatches(client, args, signal) {
|
||||
const projectId = str2(args, "project_id").trim();
|
||||
const taskId = str2(args, "task_id").trim();
|
||||
const batchId = str2(args, "batch_id").trim();
|
||||
const kindRaw = str2(args, "kind", "auto").trim().toLowerCase() || "auto";
|
||||
const includeRuns = args.include_runs === true || args.include_runs === "true" || args.include_runs === 1;
|
||||
const status = str2(args, "status").trim().toLowerCase();
|
||||
const limit = clampInt(num2(args, "limit") ?? 50, 50, 1, 200);
|
||||
const runsLimit = clampInt(num2(args, "runs_limit") ?? 10, 10, 1, 50);
|
||||
let kind = kindRaw;
|
||||
if (kind === "auto") {
|
||||
if (batchId && !projectId && !taskId)
|
||||
kind = "runs";
|
||||
else if (projectId && !taskId)
|
||||
kind = "cutover";
|
||||
else if (taskId && !projectId)
|
||||
kind = "collect";
|
||||
else if (projectId && taskId)
|
||||
kind = "all";
|
||||
else if (batchId)
|
||||
kind = "runs";
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: "project_id_or_task_id_or_batch_id_required",
|
||||
detail: "Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs)."
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!["auto", "all", "cutover", "collect", "runs"].includes(kind)) {
|
||||
return { ok: false, error: "kind_invalid", detail: "kind must be auto|all|cutover|collect|runs" };
|
||||
}
|
||||
const wantCutover = kind === "all" || kind === "cutover";
|
||||
const wantCollect = kind === "all" || kind === "collect";
|
||||
const wantRuns = kind === "runs" || includeRuns && (wantCutover || Boolean(batchId));
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: "project_id_required", detail: "kind=cutover|all needs project_id" };
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: "task_id_required", detail: "kind=collect|all needs task_id" };
|
||||
}
|
||||
if (kind === "runs" && !batchId) {
|
||||
return { ok: false, error: "batch_id_required", detail: "kind=runs needs cutover batch_id" };
|
||||
}
|
||||
let cutoverBatches = [];
|
||||
let collectBatches = [];
|
||||
let runs = [];
|
||||
if (wantCutover) {
|
||||
const res = await client.get(`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`, {}, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
cutoverBatches = items.map((row) => slimCutoverBatch(asRecord(row))).filter((row) => {
|
||||
if (batchId && String(row.id || "") !== batchId)
|
||||
return false;
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantCollect) {
|
||||
const res = await client.get(`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`, { limit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
collectBatches = items.map((row) => slimCollectBatch(asRecord(row), taskId)).filter((row) => {
|
||||
if (status && String(row.status || "").toLowerCase() !== status)
|
||||
return false;
|
||||
return true;
|
||||
}).slice(0, limit);
|
||||
}
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === "runs" ? [batchId] : cutoverBatches.map((b) => String(b.id || "").trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0);
|
||||
if (kind === "runs" && batchId) {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return res;
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit);
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(runBatchIds.map(async (id) => {
|
||||
const res = await client.get(`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`, { limit: runsLimit }, signal);
|
||||
if (res.ok === false)
|
||||
return { batch_id: id, error: res, items: [] };
|
||||
const items = Array.isArray(asRecord(res.data).items) ? asRecord(res.data).items : [];
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
};
|
||||
}));
|
||||
for (const block of settled) {
|
||||
if ("error" in block && block.error) {
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (failed)
|
||||
failed.runs_error = asRecord(block.error).error || "runs_fetch_failed";
|
||||
continue;
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id);
|
||||
if (target)
|
||||
target.runs = block.items;
|
||||
for (const r of block.items)
|
||||
runs.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === "runs" ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length
|
||||
},
|
||||
next: wantCutover ? "Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch." : wantCollect ? "Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw." : "Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id)."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -4725,13 +4895,23 @@ function registerNetxTools(ctx, connection, options) {
|
|||
layer: str4(),
|
||||
detail: str4("summary | full")
|
||||
}, findTopologyPaths, getClient, t),
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.", {
|
||||
tool2("netx__listBizMonitors", "Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.", {
|
||||
kind: str4("all | projects | tasks (default all)"),
|
||||
purpose: str4("Filter tasks: portrait | cutover_hf | empty=all"),
|
||||
status: str4("Filter by status (e.g. active / paused)"),
|
||||
q: str4("Keyword on name / ne_name / note / id"),
|
||||
limit: num4("Max rows per list (default 100, max 500)")
|
||||
}, listBizMonitors, getClient, t),
|
||||
tool2("netx__listBizMonitorBatches", "List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.", {
|
||||
kind: str4("auto | all | cutover | collect | runs (default auto)"),
|
||||
project_id: str4("Cutover project id → cutover_batches"),
|
||||
task_id: str4("biz_state task id → collect_batches"),
|
||||
batch_id: str4("Cutover batch id (filter or kind=runs)"),
|
||||
include_runs: bool4("Attach recent evaluate runs onto listed cutover batches"),
|
||||
status: str4("Filter batch status"),
|
||||
limit: num4("Max batches (default 50)"),
|
||||
runs_limit: num4("Max runs per batch when include_runs / kind=runs (default 10)")
|
||||
}, listBizMonitorBatches, getClient, t),
|
||||
tool2("netx__getBizMonitorContext", "Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. 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)")
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
name: netx-biz-monitor
|
||||
description: >-
|
||||
Overnight cutover / biz_state monitor analysis via netxops host tools
|
||||
(netx__listBizMonitors / netx__getBizMonitor*). List projects/tasks first
|
||||
when no id is given; then 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__listBizMonitors / listBizMonitorBatches / getBizMonitor*). List
|
||||
projects/tasks then batches when no id is given; 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(割接 / 业务监控只读分析)
|
||||
|
|
@ -18,6 +18,7 @@ description: >-
|
|||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| `netx__listBizMonitors` | **入口目录**:列出割接/监控对比项目 + 业务监控任务(**无需** project_id) |
|
||||
| `netx__listBizMonitorBatches` | **批次目录**:`project_id`→割接批次;`task_id`→采集批次;`kind=runs`→evaluate run |
|
||||
| `netx__getBizMonitorContext` | 任务定义:项目、监控/对比模板、归一化规则、端口映射、关联任务与命令项 |
|
||||
| `netx__getBizMonitorBoard` | 批次进度、evaluate run、sheet 卡、verdict 统计 |
|
||||
| `netx__listBizMonitorReds` | 红单 + **evidence** |
|
||||
|
|
@ -37,12 +38,13 @@ description: >-
|
|||
## 推荐顺序
|
||||
|
||||
1. 无 id → `listBizMonitors`(`kind=projects|tasks|all`,可加 `q` / `purpose`)
|
||||
2. `getBizMonitorContext(project_id)` — 弄清盯什么
|
||||
3. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
|
||||
4. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
|
||||
5. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
|
||||
6. 需要原文 → `getBizCollectCommandRaw`
|
||||
7. 像真障 → `execManagedNe` 现场核对
|
||||
2. `listBizMonitorBatches(project_id)` 或 `(task_id)` — 拿到 **完整 batch_id**;需要 run 时再 `kind=runs` / `include_runs`
|
||||
3. `getBizMonitorContext(project_id)` — 弄清盯什么
|
||||
4. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
|
||||
5. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
|
||||
6. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
|
||||
7. 需要原文 → `getBizCollectCommandRaw`
|
||||
8. 像真障 → `execManagedNe` 现场核对
|
||||
|
||||
## Verdict 速查(skill 解释,工具不硬编码)
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export const TOOLS_BY_GROUP: Readonly<Record<NetxCapabilityGroupId, readonly str
|
|||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
'netx__listBizMonitors',
|
||||
'netx__listBizMonitorBatches',
|
||||
'netx__getBizMonitorContext',
|
||||
'netx__getBizMonitorBoard',
|
||||
'netx__listBizMonitorReds',
|
||||
|
|
|
|||
|
|
@ -450,7 +450,220 @@ export async function listBizMonitors(
|
|||
projects_returned: wantProjects ? projects.length : 0,
|
||||
tasks_returned: wantTasks ? tasks.length : 0,
|
||||
},
|
||||
next: 'Pass project_id (or task_id) to netx__getBizMonitorContext for the full definition bundle.',
|
||||
next: 'Pass project_id (or task_id) to netx__getBizMonitorContext; then listBizMonitorBatches for batch_id / run_id.',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function slimCutoverBatch(row: NetxJson): NetxJson {
|
||||
return {
|
||||
id: row.id,
|
||||
project_id: row.project_id,
|
||||
batch_label: row.batch_label,
|
||||
status: row.status,
|
||||
accept_status: row.accept_status,
|
||||
accept_run_id: row.accept_run_id,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
note: row.note,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
function slimCollectBatch(row: NetxJson, taskId = ''): NetxJson {
|
||||
return {
|
||||
id: row.id,
|
||||
task_id: row.task_id || taskId,
|
||||
status: row.status,
|
||||
ne_name: row.ne_name,
|
||||
command_count: row.command_count,
|
||||
row_count: row.row_count,
|
||||
is_baseline: row.is_baseline,
|
||||
protected: row.protected,
|
||||
message: row.message,
|
||||
started_at: row.started_at,
|
||||
ended_at: row.ended_at,
|
||||
}
|
||||
}
|
||||
|
||||
function slimEvalRun(row: NetxJson): NetxJson {
|
||||
const summary = asRecord(row.summary)
|
||||
const progress = asRecord(summary.progress)
|
||||
return {
|
||||
id: row.id,
|
||||
batch_id: row.batch_id,
|
||||
purpose: row.purpose,
|
||||
old_batch_id: row.old_batch_id,
|
||||
new_batch_id: row.new_batch_id,
|
||||
created_at: row.created_at,
|
||||
anomaly: summary.anomaly,
|
||||
progress_ok: progress.ok ?? summary.progress_ok,
|
||||
progress_total: progress.total ?? summary.progress_total,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List cutover batches, biz_state collect batches, and/or evaluate runs.
|
||||
* Needs project_id and/or task_id and/or cutover batch_id (for runs).
|
||||
*/
|
||||
export async function listBizMonitorBatches(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const projectId = str(args, 'project_id').trim()
|
||||
const taskId = str(args, 'task_id').trim()
|
||||
const batchId = str(args, 'batch_id').trim()
|
||||
const kindRaw = str(args, 'kind', 'auto').trim().toLowerCase() || 'auto'
|
||||
const includeRuns = args.include_runs === true || args.include_runs === 'true' || args.include_runs === 1
|
||||
const status = str(args, 'status').trim().toLowerCase()
|
||||
const limit = clampInt(num(args, 'limit') ?? 50, 50, 1, 200)
|
||||
const runsLimit = clampInt(num(args, 'runs_limit') ?? 10, 10, 1, 50)
|
||||
|
||||
let kind = kindRaw
|
||||
if (kind === 'auto') {
|
||||
if (batchId && !projectId && !taskId) kind = 'runs'
|
||||
else if (projectId && !taskId) kind = 'cutover'
|
||||
else if (taskId && !projectId) kind = 'collect'
|
||||
else if (projectId && taskId) kind = 'all'
|
||||
else if (batchId) kind = 'runs'
|
||||
else {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'project_id_or_task_id_or_batch_id_required',
|
||||
detail: 'Pass project_id (cutover batches), task_id (collect batches), and/or batch_id (evaluate runs).',
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!['auto', 'all', 'cutover', 'collect', 'runs'].includes(kind)) {
|
||||
return { ok: false, error: 'kind_invalid', detail: 'kind must be auto|all|cutover|collect|runs' }
|
||||
}
|
||||
|
||||
const wantCutover = kind === 'all' || kind === 'cutover'
|
||||
const wantCollect = kind === 'all' || kind === 'collect'
|
||||
const wantRuns = kind === 'runs' || (includeRuns && (wantCutover || Boolean(batchId)))
|
||||
|
||||
if (wantCutover && !projectId) {
|
||||
return { ok: false, error: 'project_id_required', detail: 'kind=cutover|all needs project_id' }
|
||||
}
|
||||
if (wantCollect && !taskId) {
|
||||
return { ok: false, error: 'task_id_required', detail: 'kind=collect|all needs task_id' }
|
||||
}
|
||||
if (kind === 'runs' && !batchId) {
|
||||
return { ok: false, error: 'batch_id_required', detail: 'kind=runs needs cutover batch_id' }
|
||||
}
|
||||
|
||||
let cutoverBatches: NetxJson[] = []
|
||||
let collectBatches: NetxJson[] = []
|
||||
let runs: NetxJson[] = []
|
||||
|
||||
if (wantCutover) {
|
||||
const res = await client.get(
|
||||
`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/batches`,
|
||||
{},
|
||||
signal,
|
||||
)
|
||||
if (res.ok === false) return res
|
||||
const items = Array.isArray(asRecord(res.data).items)
|
||||
? (asRecord(res.data).items as unknown[])
|
||||
: []
|
||||
cutoverBatches = items
|
||||
.map((row) => slimCutoverBatch(asRecord(row)))
|
||||
.filter((row) => {
|
||||
if (batchId && String(row.id || '') !== batchId) return false
|
||||
if (status && String(row.status || '').toLowerCase() !== status) return false
|
||||
return true
|
||||
})
|
||||
.slice(0, limit)
|
||||
}
|
||||
|
||||
if (wantCollect) {
|
||||
const res = await client.get(
|
||||
`/v1/biz-state/tasks/${encodeURIComponent(taskId)}/batches`,
|
||||
{ limit },
|
||||
signal,
|
||||
)
|
||||
if (res.ok === false) return res
|
||||
const items = Array.isArray(asRecord(res.data).items)
|
||||
? (asRecord(res.data).items as unknown[])
|
||||
: []
|
||||
collectBatches = items
|
||||
.map((row) => slimCollectBatch(asRecord(row), taskId))
|
||||
.filter((row) => {
|
||||
if (status && String(row.status || '').toLowerCase() !== status) return false
|
||||
return true
|
||||
})
|
||||
.slice(0, limit)
|
||||
}
|
||||
|
||||
if (wantRuns) {
|
||||
const runBatchIds = kind === 'runs'
|
||||
? [batchId]
|
||||
: cutoverBatches.map((b) => String(b.id || '').trim()).filter(Boolean).slice(0, includeRuns ? 5 : 0)
|
||||
if (kind === 'runs' && batchId) {
|
||||
const res = await client.get(
|
||||
`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/runs`,
|
||||
{ limit: runsLimit },
|
||||
signal,
|
||||
)
|
||||
if (res.ok === false) return res
|
||||
const items = Array.isArray(asRecord(res.data).items)
|
||||
? (asRecord(res.data).items as unknown[])
|
||||
: []
|
||||
runs = items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit)
|
||||
} else if (includeRuns && runBatchIds.length > 0) {
|
||||
const settled = await Promise.all(
|
||||
runBatchIds.map(async (id) => {
|
||||
const res = await client.get(
|
||||
`/v1/biz-migration/batches/${encodeURIComponent(id)}/runs`,
|
||||
{ limit: runsLimit },
|
||||
signal,
|
||||
)
|
||||
if (res.ok === false) return { batch_id: id, error: res, items: [] as NetxJson[] }
|
||||
const items = Array.isArray(asRecord(res.data).items)
|
||||
? (asRecord(res.data).items as unknown[])
|
||||
: []
|
||||
return {
|
||||
batch_id: id,
|
||||
items: items.map((row) => slimEvalRun(asRecord(row))).slice(0, runsLimit),
|
||||
}
|
||||
}),
|
||||
)
|
||||
for (const block of settled) {
|
||||
if ('error' in block && block.error) {
|
||||
// Keep listing batches even if one runs fetch fails; attach error note.
|
||||
const failed = cutoverBatches.find((b) => b.id === block.batch_id)
|
||||
if (failed) failed.runs_error = asRecord(block.error).error || 'runs_fetch_failed'
|
||||
continue
|
||||
}
|
||||
const target = cutoverBatches.find((b) => b.id === block.batch_id)
|
||||
if (target) target.runs = block.items
|
||||
for (const r of block.items) runs.push(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
kind,
|
||||
project_id: projectId || undefined,
|
||||
task_id: taskId || undefined,
|
||||
batch_id: batchId || undefined,
|
||||
cutover_batches: wantCutover ? cutoverBatches : undefined,
|
||||
collect_batches: wantCollect ? collectBatches : undefined,
|
||||
runs: kind === 'runs' ? runs : undefined,
|
||||
counts: {
|
||||
cutover_batches: cutoverBatches.length,
|
||||
collect_batches: collectBatches.length,
|
||||
runs: runs.length,
|
||||
},
|
||||
next: wantCutover
|
||||
? 'Use cutover batch id with netx__getBizMonitorBoard / listBizMonitorBatches(kind=runs). Collect batch id → netx__getBizCollectBatch.'
|
||||
: wantCollect
|
||||
? 'Pass collect batch id to netx__getBizCollectBatch / getBizCollectCommandRaw.'
|
||||
: 'Pass run_id to netx__getBizMonitorDiffs or getBizMonitorBoard(run_id).',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ export function registerNetxTools(
|
|||
// ── bizMonitor group (cutover / biz_state read via netx REST) ─────────
|
||||
tool(
|
||||
'netx__listBizMonitors',
|
||||
'Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then use getBizMonitorContext.',
|
||||
'Catalog: list cutover/monitor-compare projects and biz_state monitor tasks. No project_id needed — call this first when the user did not give an id, then listBizMonitorBatches / getBizMonitorContext.',
|
||||
{
|
||||
kind: str('all | projects | tasks (default all)'),
|
||||
purpose: str('Filter tasks: portrait | cutover_hf | empty=all'),
|
||||
|
|
@ -329,6 +329,21 @@ export function registerNetxTools(
|
|||
},
|
||||
H.listBizMonitors, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__listBizMonitorBatches',
|
||||
'List batch ids for further analysis. project_id → cutover batches; task_id → biz_state collect batches; batch_id + kind=runs → evaluate runs. Optional include_runs attaches recent runs onto cutover batches.',
|
||||
{
|
||||
kind: str('auto | all | cutover | collect | runs (default auto)'),
|
||||
project_id: str('Cutover project id → cutover_batches'),
|
||||
task_id: str('biz_state task id → collect_batches'),
|
||||
batch_id: str('Cutover batch id (filter or kind=runs)'),
|
||||
include_runs: bool('Attach recent evaluate runs onto listed cutover batches'),
|
||||
status: str('Filter batch status'),
|
||||
limit: num('Max batches (default 50)'),
|
||||
runs_limit: num('Max runs per batch when include_runs / kind=runs (default 10)'),
|
||||
},
|
||||
H.listBizMonitorBatches, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__getBizMonitorContext',
|
||||
'Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id from listBizMonitors. Display keys are device-raw A/B.',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue