- 新增 pam_deploy_graph 包,包含 Agent runtime、ActionRouter、脚本/MCP/fake runner - 支持 hybrid_node_mcp 策略:PAM_HOME 走脚本 action,PAM_NODE 走 MCP - 支持 script_only 离线策略,全部 action 走现有脚本 action - 新增 LLM structured output 骨架和规则 fallback,支持意图识别、参数抽取、计划生成 - 新增 LangGraph StateGraph 工厂和 MCP client adapter - 新增 CLI:preview、analyze、run-global、run-deploy - 增加 fake 完整部署流程、单 IP 失败待回滚确认状态和报告输出 - 增加单元测试覆盖路由、parser、runner、Skill 加载、LLM 输出、MCP adapter 和 LangGraph 图 - 更新 README,记录当前代码骨架、进度、使用方式和下一步计划
65 lines
2.6 KiB
Python
65 lines
2.6 KiB
Python
"""Fake action runner for graph and agent tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from .models import ActionResult
|
|
|
|
|
|
class FakeActionRunner:
|
|
def __init__(self, fixtures: dict[str, dict[str, Any]] | None = None) -> None:
|
|
self.fixtures = fixtures or {}
|
|
self.calls: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def run(self, action: str, *, params: dict[str, Any], **kwargs: Any) -> ActionResult:
|
|
self.calls.append((action, kwargs))
|
|
values = self._fixture_for(action, kwargs)
|
|
if not values:
|
|
values = self._default_values(action, kwargs)
|
|
ok = not values.pop("_fail", False)
|
|
return ActionResult(
|
|
action=action,
|
|
backend="fake",
|
|
tool_name=f"fake:{action}",
|
|
ok=ok,
|
|
values=values,
|
|
exit_code=0 if ok else 1,
|
|
raw_output=str(values),
|
|
error_summary="" if ok else str(values.get("MESSAGE", "Fake action failed")),
|
|
)
|
|
|
|
def _default_values(self, action: str, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
if action == "get-token":
|
|
return {"ACTION": action, "TOKEN": "***"}
|
|
if action == "upload-package":
|
|
return {"ACTION": action, "HASH_CODE": "fake-hash"}
|
|
if action == "get-node-url":
|
|
return {"ACTION": action, "NODE_URL": "https://fake-node.local"}
|
|
if action == "get-online-ips":
|
|
return {"ACTION": action, "COUNT": "2", "IP": ["192.168.1.10", "192.168.1.11"]}
|
|
if action == "upgrade-ip":
|
|
return {"ACTION": action, "IP": kwargs.get("ip", ""), "RESULT": "TASK_CREATED"}
|
|
if action == "poll-upgrade-progress":
|
|
return {
|
|
"ACTION": action,
|
|
"IP": kwargs.get("ip", ""),
|
|
"STEP": "DONE",
|
|
"RATE_OF_PROGRESS": "100",
|
|
"MESSAGE": "success",
|
|
}
|
|
if action == "start-ip":
|
|
return {"ACTION": action, "IP": kwargs.get("ip", ""), "RESULT": "OK"}
|
|
if action == "verify-ip":
|
|
return {"ACTION": action, "IP": kwargs.get("ip", ""), "SUCCESS": "true", "MESSAGE": "ok"}
|
|
if action == "download-log":
|
|
return {"ACTION": action, "IP": kwargs.get("ip", ""), "LOG_FILE": "logs/fake.zip"}
|
|
return {"ACTION": action, "RESULT": "OK"}
|
|
|
|
def _fixture_for(self, action: str, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
ip = kwargs.get("ip")
|
|
ip_key = f"{action}:{ip}" if ip else ""
|
|
if ip_key and ip_key in self.fixtures:
|
|
return self.fixtures[ip_key].copy()
|
|
return self.fixtures.get(action, {}).copy()
|