mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 05:50:44 +08:00
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
192 lines
6.3 KiB
Python
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,
|
|
}
|
|
|