oclaw/interfaces/gateway/server_methods/exec_approvals.py
oliver 4a23b715a2 重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。

Made-with: Cursor
2026-04-25 01:24:23 +08:00

192 lines
6.3 KiB
Python

from __future__ import annotations
from typing import Any
from .shared_types import GatewayRequestHandlers
from .validation import error_shape
def _ok(respond, payload: Any) -> None:
if callable(respond):
respond(True, payload, None, None)
def _bad(respond, message: str) -> None:
if callable(respond):
respond(False, None, error_shape("INVALID_REQUEST", message), None)
def _unavailable(respond, message: str) -> None:
if callable(respond):
respond(False, None, error_shape("UNAVAILABLE", message), None)
def _norm_str(v: Any) -> str | None:
if isinstance(v, str):
s = v.strip()
return s or None
return None
def _state(context: Any) -> dict[str, Any]:
if not isinstance(context, dict):
return {"path": ".oclaw/exec-approvals.json", "exists": False, "hash": None, "file": {}}
st = context.get("_exec_approvals_state")
if isinstance(st, dict):
return st
created = {"path": ".oclaw/exec-approvals.json", "exists": False, "hash": None, "file": {}}
context["_exec_approvals_state"] = created
return created
def _payload(st: dict[str, Any]) -> dict[str, Any]:
file_obj = st.get("file")
file_obj = dict(file_obj) if isinstance(file_obj, dict) else {}
socket = file_obj.get("socket")
if isinstance(socket, dict):
path = _norm_str(socket.get("path"))
file_obj["socket"] = {"path": path} if path else None
return {
"path": st.get("path"),
"exists": bool(st.get("exists", False)),
"hash": st.get("hash"),
"file": file_obj,
}
def _require_base_hash(params: dict[str, Any], st: dict[str, Any], respond) -> bool:
if not bool(st.get("exists")):
return True
snap_hash = _norm_str(st.get("hash"))
if not snap_hash:
_bad(respond, "exec approvals base hash unavailable; re-run exec.approvals.get and retry")
return False
base_hash = _norm_str(params.get("baseHash"))
if not base_hash:
_bad(respond, "exec approvals base hash required; re-run exec.approvals.get and retry")
return False
if base_hash != snap_hash:
_bad(respond, "exec approvals changed since last load; re-run exec.approvals.get and retry")
return False
return True
def _resolve_node_id_or_error(raw: Any, respond) -> str | None:
node_id = _norm_str(raw)
if not node_id:
_bad(respond, "nodeId required")
return None
return node_id
def _exec_approvals_get_handler(opts: dict[str, Any]) -> None:
respond = opts.get("respond")
context = opts.get("context")
hook = context.get("read_exec_approvals_snapshot") if isinstance(context, dict) else None
if callable(hook):
try:
snap = hook()
if isinstance(snap, dict):
_ok(respond, _payload(snap))
return
except Exception as exc:
_unavailable(respond, str(exc))
return
st = _state(context)
_ok(respond, _payload(st))
def _exec_approvals_set_handler(opts: dict[str, Any]) -> None:
params = opts.get("params") or {}
respond = opts.get("respond")
context = opts.get("context")
if not isinstance(params, dict):
_bad(respond, "invalid exec.approvals.set params")
return
st = _state(context)
if not _require_base_hash(params, st, respond):
return
incoming = params.get("file")
if not isinstance(incoming, dict):
_bad(respond, "exec approvals file is required")
return
hook = context.get("write_exec_approvals") if isinstance(context, dict) else None
if callable(hook):
try:
next_snap = hook({"file": incoming, "baseHash": params.get("baseHash")})
if isinstance(next_snap, dict):
_ok(respond, _payload(next_snap))
return
except Exception as exc:
_unavailable(respond, str(exc))
return
st["file"] = dict(incoming)
st["exists"] = True
st["hash"] = f"h{abs(hash(str(incoming))) % 1000000}"
_ok(respond, _payload(st))
def _exec_approvals_node_get_handler(opts: dict[str, Any]) -> None:
params = opts.get("params") or {}
respond = opts.get("respond")
context = opts.get("context")
if not isinstance(params, dict):
_bad(respond, "invalid exec.approvals.node.get params")
return
node_id = _resolve_node_id_or_error(params.get("nodeId"), respond)
if not node_id:
return
hook = context.get("node_exec_approvals_get") if isinstance(context, dict) else None
if callable(hook):
try:
payload = hook({"nodeId": node_id})
_ok(respond, payload if payload is not None else {})
return
except Exception as exc:
_unavailable(respond, str(exc))
return
_ok(respond, {"nodeId": node_id, "path": None, "exists": False, "hash": None, "file": {}})
def _exec_approvals_node_set_handler(opts: dict[str, Any]) -> None:
params = opts.get("params") or {}
respond = opts.get("respond")
context = opts.get("context")
if not isinstance(params, dict):
_bad(respond, "invalid exec.approvals.node.set params")
return
node_id = _resolve_node_id_or_error(params.get("nodeId"), respond)
if not node_id:
return
file_obj = params.get("file")
if not isinstance(file_obj, dict):
_bad(respond, "exec approvals file is required")
return
hook = context.get("node_exec_approvals_set") if isinstance(context, dict) else None
if callable(hook):
try:
payload = hook({"nodeId": node_id, "file": file_obj, "baseHash": params.get("baseHash")})
_ok(respond, payload if payload is not None else {})
return
except Exception as exc:
_unavailable(respond, str(exc))
return
_ok(
respond,
{
"nodeId": node_id,
"path": None,
"exists": True,
"hash": f"h{abs(hash(str(file_obj))) % 1000000}",
"file": dict(file_obj),
},
)
exec_approvals_handlers: GatewayRequestHandlers = {
"exec.approvals.get": _exec_approvals_get_handler,
"exec.approvals.set": _exec_approvals_set_handler,
"exec.approvals.node.get": _exec_approvals_node_get_handler,
"exec.approvals.node.set": _exec_approvals_node_set_handler,
}