feat: implement configurable Xingbang risk agent on mini-openclaw core
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""Tools scoped to an immutable authorized risk run, not the production database."""
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from langchain_core.tools import tool
|
||||
|
||||
|
||||
def create_risk_tools(context, evidence, native_skills):
|
||||
snapshot = deepcopy(context)
|
||||
sources = deepcopy(evidence)
|
||||
skills = deepcopy(native_skills)
|
||||
|
||||
@tool
|
||||
def list_data_cards() -> str:
|
||||
"""List authorized peer data-card snapshots available for this risk task."""
|
||||
return json.dumps([{"index":i,"name":d["card"],"version":d["version"],"records":len(d["records"])} for i,d in enumerate(snapshot.get("peer_datasets",[]))],ensure_ascii=False)
|
||||
|
||||
@tool
|
||||
def read_data_card(index: int) -> str:
|
||||
"""Read one authorized data-card snapshot, including periods and raw evidence."""
|
||||
cards = snapshot.get("peer_datasets", [])
|
||||
if index < 0 or index >= len(cards):
|
||||
return json.dumps({"error":"数据卡不在本次授权范围"},ensure_ascii=False)
|
||||
return json.dumps(cards[index],ensure_ascii=False)
|
||||
|
||||
@tool
|
||||
def read_checks_and_rules() -> str:
|
||||
"""Read deterministic Skill outputs, human rules and rule findings for this run."""
|
||||
return json.dumps({"values":snapshot.get("scalar_values",{}),"human_rules":snapshot.get("human_rules",[]),"findings":snapshot.get("rule_findings",[]),"evidence":sources},ensure_ascii=False)
|
||||
|
||||
@tool
|
||||
def read_selected_skill(skill_id: str) -> str:
|
||||
"""Read only a native SKILL.md explicitly selected in the published plan."""
|
||||
return skills.get(skill_id,"此 Skill 未被所选方案授权")
|
||||
|
||||
return [list_data_cards, read_data_card, read_checks_and_rules, read_selected_skill]
|
||||
@@ -5,6 +5,28 @@ from pathlib import Path
|
||||
import yaml
|
||||
|
||||
|
||||
def discover_skills(base_dir: Path) -> list[dict]:
|
||||
"""Read the native Skills catalog without changing its generated snapshot."""
|
||||
root = (base_dir / "skills").resolve()
|
||||
entries = []
|
||||
for file in sorted(root.rglob("SKILL.md")):
|
||||
resolved = file.resolve()
|
||||
if not resolved.is_relative_to(root):
|
||||
continue
|
||||
try:
|
||||
content = file.read_text(encoding="utf-8")
|
||||
if not content.startswith("---"):
|
||||
continue
|
||||
parts = content.split("---", 2)
|
||||
meta = yaml.safe_load(parts[1]) if len(parts) == 3 else None
|
||||
if not isinstance(meta, dict):
|
||||
continue
|
||||
entries.append({"id":file.relative_to(root).as_posix(),"name":str(meta.get("name",file.parent.name)),"description":str(meta.get("description","")),"content":content})
|
||||
except (OSError, ValueError, yaml.YAMLError):
|
||||
continue
|
||||
return entries
|
||||
|
||||
|
||||
def scan_skills(base_dir: Path) -> str:
|
||||
"""Scan all SKILL.md files and generate SKILLS_SNAPSHOT.md."""
|
||||
skills_dir = base_dir / "skills"
|
||||
@@ -13,24 +35,7 @@ def scan_skills(base_dir: Path) -> str:
|
||||
if not skills_dir.exists():
|
||||
skills_dir.mkdir(parents=True)
|
||||
|
||||
skills = []
|
||||
for skill_md in sorted(skills_dir.rglob("SKILL.md")):
|
||||
try:
|
||||
content = skill_md.read_text(encoding="utf-8")
|
||||
# Parse YAML frontmatter
|
||||
if content.startswith("---"):
|
||||
parts = content.split("---", 2)
|
||||
if len(parts) >= 3:
|
||||
meta = yaml.safe_load(parts[1])
|
||||
if meta:
|
||||
rel_path = f"./backend/skills/{skill_md.parent.name}/SKILL.md"
|
||||
skills.append({
|
||||
"name": meta.get("name", skill_md.parent.name),
|
||||
"description": meta.get("description", ""),
|
||||
"location": rel_path,
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error scanning {skill_md}: {e}")
|
||||
skills = [{**s, "location":f"./backend/skills/{s['id']}"} for s in discover_skills(base_dir)]
|
||||
|
||||
# Build XML-style snapshot
|
||||
lines = ["<available_skills>"]
|
||||
|
||||
Reference in New Issue
Block a user