oclaw/gateway/server_methods/update.py
oliver ba3836f00f 初始化:独立 oclaw 仓库首提交
- 在 oclaw/ 下重新初始化 Git 仓库
- 补齐子仓库 .gitignore,避免提交本地运行态数据(_local、node_modules、logs 等)
- 提交当前工程代码与配置

Made-with: Cursor
2026-04-24 22:31:22 +08:00

148 lines
4.8 KiB
Python

from __future__ import annotations
import time
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 _norm_str(v: Any) -> str | None:
if isinstance(v, str):
s = v.strip()
return s or None
return None
def _as_int(v: Any) -> int | None:
if isinstance(v, bool):
return None
if isinstance(v, (int, float)):
return int(v)
return None
def _parse_restart_request_params(params: dict[str, Any]) -> dict[str, Any]:
return {
"sessionKey": _norm_str(params.get("sessionKey")),
"deliveryContext": params.get("deliveryContext") if isinstance(params.get("deliveryContext"), dict) else None,
"threadId": _norm_str(params.get("threadId")),
"note": _norm_str(params.get("note")),
"restartDelayMs": max(0, _as_int(params.get("restartDelayMs")) or 0) or None,
}
def _update_run_handler(opts: dict[str, Any]) -> None:
params = opts.get("params")
respond = opts.get("respond")
context = opts.get("context")
client = opts.get("client")
if params is not None and not isinstance(params, dict):
_bad(respond, "invalid update.run params")
return
p = dict(params or {})
parsed = _parse_restart_request_params(p)
timeout_ms_raw = _as_int(p.get("timeoutMs"))
timeout_ms = max(1000, timeout_ms_raw) if isinstance(timeout_ms_raw, int) else None
runner = context.get("run_gateway_update") if isinstance(context, dict) else None
if callable(runner):
try:
result = runner({"timeoutMs": timeout_ms, "params": p})
if not isinstance(result, dict):
result = {
"status": "ok",
"mode": "unknown",
"steps": [],
"durationMs": 0,
}
except Exception as exc:
result = {
"status": "error",
"mode": "unknown",
"reason": str(exc),
"steps": [],
"durationMs": 0,
}
else:
result = {
"status": "ok",
"mode": "staging",
"steps": [],
"durationMs": 0,
}
payload = {
"kind": "update",
"status": result.get("status"),
"ts": int(time.time() * 1000),
"sessionKey": parsed["sessionKey"],
"deliveryContext": parsed["deliveryContext"],
"threadId": parsed["threadId"],
"message": parsed["note"],
"doctorHint": "Run doctor in non-interactive mode if needed.",
"stats": {
"mode": result.get("mode"),
"root": result.get("root"),
"before": result.get("before"),
"after": result.get("after"),
"steps": result.get("steps") if isinstance(result.get("steps"), list) else [],
"reason": result.get("reason"),
"durationMs": result.get("durationMs"),
},
}
sentinel_path = None
write_sentinel = context.get("write_restart_sentinel") if isinstance(context, dict) else None
if callable(write_sentinel):
try:
sentinel_path = write_sentinel(payload)
except Exception:
sentinel_path = None
restart = None
if result.get("status") == "ok":
schedule = context.get("schedule_gateway_restart") if isinstance(context, dict) else None
if callable(schedule):
actor = {"actor": "unknown", "deviceId": None, "clientIp": None}
if isinstance(client, dict):
connect = client.get("connect")
if isinstance(connect, dict):
actor["deviceId"] = connect.get("device", {}).get("id") if isinstance(connect.get("device"), dict) else None
actor["clientIp"] = client.get("client_ip")
restart = schedule(
{
"delayMs": parsed["restartDelayMs"],
"reason": "update.run",
"audit": {"actor": actor.get("actor"), "deviceId": actor.get("deviceId"), "clientIp": actor.get("clientIp"), "changedPaths": []},
}
)
else:
restart = {"scheduled": True, "reason": "update.run", "delayMs": parsed["restartDelayMs"]}
_ok(
respond,
{
"ok": result.get("status") != "error",
"result": result,
"restart": restart,
"sentinel": {"path": sentinel_path, "payload": payload},
},
)
update_handlers: GatewayRequestHandlers = {
"update.run": _update_run_handler,
}