Enforce report-first short intents and async large execManagedNe batches.

Block CLI/inventory until ume_alarm_xlsx_report (or aggregate) succeeds, and return job_id for big NE batches via get_ne_exec_job.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-12 22:17:16 +08:00
parent 366bc2a320
commit 4827278a9c
11 changed files with 695 additions and 34 deletions

View file

@ -0,0 +1,161 @@
"""Report-first gate for ops short intents: CLI/inventory only after report path."""
from __future__ import annotations
import json
from typing import Any
from runtime.application.gateway.ops_short_intent import ops_short_intent_should_filter_tools
from runtime.tools.playbook_contracts import playbook_example_for_tool, short_intent_first_step
# Tools blocked until a report-path tool succeeds this turn.
_CLI_BEFORE_REPORT_TOOLS = frozenset(
{
"listclitargets",
"listmanagedne",
"getmanagedne",
"execmanagedne",
"queryumeneinventory",
"getumene",
"findtopologypaths",
"sqlqueryume",
"netx_list_managed_ne",
"netx_get_managed_ne",
"netx_exec_managed_ne",
"netx_sql_query_ume",
"netx_list_cli_targets",
}
)
# Successful call to any of these unlocks CLI for the rest of the turn.
_REPORT_PATH_TOOLS = frozenset(
{
"ume_alarm_xlsx_report",
"aggregateumealarms",
"aggregateumealarmsraw",
}
)
def _tool_key(name: str) -> str:
raw = str(name or "").strip()
if not raw:
return ""
if "__" in raw:
raw = raw.rsplit("__", 1)[-1]
return raw.strip().lower().replace("-", "_")
def is_cli_before_report_tool(tool_name: str) -> bool:
return _tool_key(tool_name) in _CLI_BEFORE_REPORT_TOOLS
def is_report_path_tool(tool_name: str) -> bool:
return _tool_key(tool_name) in _REPORT_PATH_TOOLS
def _parse_json_obj(raw: Any) -> dict[str, Any]:
if isinstance(raw, dict):
return dict(raw)
if isinstance(raw, str) and raw.strip():
try:
data = json.loads(raw)
return data if isinstance(data, dict) else {}
except Exception:
return {}
return {}
def turn_has_successful_report_path(
store: Any,
*,
session_id: str,
turn_uuid: str,
) -> bool:
"""True if this turn already has a successful report/aggregate tool result."""
tu = str(turn_uuid or "").strip()
sid = str(session_id or "").strip()
if not tu or not sid or store is None:
return False
try:
rows = store.get_messages(session_id=sid, limit=500)
except Exception:
return False
for m in rows or []:
if str(getattr(m, "role", "") or "").strip().lower() != "tool":
continue
if str(getattr(m, "turn_uuid", "") or "").strip() != tu:
continue
ep = _parse_json_obj(getattr(m, "event_payload", None))
name = str(ep.get("tool_name") or "").strip()
if not name:
tc = _parse_json_obj(getattr(m, "tool_calls", None))
name = str(tc.get("name") or "").strip()
if not is_report_path_tool(name):
continue
if ep.get("ok") is True:
return True
try:
payload = json.loads(str(getattr(m, "content", "") or "") or "{}")
except Exception:
payload = {}
if isinstance(payload, dict) and payload.get("ok") is True:
return True
return False
def report_first_block_payload(*, intent: str | None, lang: str = "en") -> dict[str, Any]:
en = str(lang or "").strip().lower().startswith("en")
step = short_intent_first_step(intent)
tool = "ume_alarm_xlsx_report"
example = playbook_example_for_tool(tool, intent=intent) or {"mode": "list", "deliverable": True}
if step:
tool, example = step[0], step[1]
hint = (
f"Report-first gate: call {tool} successfully before CLI/inventory this turn. "
"Device confirmation via execManagedNe is allowed only after the report path returns ok."
if en
else f"报表优先:本轮先成功调用 {tool},再允许 CLI/清单。"
"设备核实(execManagedNe)仅在报表路径 ok 之后。"
)
return {
"ok": False,
"error_code": "report_first_required",
"failure_class": "report_first",
"error": "report_first_required",
"hint": hint,
"next_tool": tool,
"example": example,
"intent": str(intent or ""),
}
def maybe_block_cli_before_report(
*,
tool_name: str,
intent: str | None,
store: Any,
session_id: str,
turn_uuid: str,
lang: str = "en",
local_report_ok: bool = False,
) -> dict[str, Any] | None:
"""Return a block payload when CLI/inventory is used before report on short intents."""
if not ops_short_intent_should_filter_tools(intent):
return None
if not is_cli_before_report_tool(tool_name):
return None
if local_report_ok:
return None
if turn_has_successful_report_path(store, session_id=session_id, turn_uuid=turn_uuid):
return None
return report_first_block_payload(intent=intent, lang=lang)
__all__ = [
"is_cli_before_report_tool",
"is_report_path_tool",
"maybe_block_cli_before_report",
"report_first_block_payload",
"turn_has_successful_report_path",
]