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

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

250 lines
8.6 KiB
Python

from __future__ import annotations
from typing import Any
from .shared_types import GatewayRequestHandlers
from .validation import error_shape
DEFAULT_AGENT_ID = "main"
ALLOWED_FILE_NAMES = {
"AGENTS.md",
"SOUL.md",
"TOOLS.md",
"IDENTITY.md",
"USER.md",
"HEARTBEAT.md",
"BOOTSTRAP.md",
"memory/README.md",
"memory.md",
}
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
if isinstance(v, (int, float)) and not isinstance(v, bool):
s = str(v).strip()
return s or None
return None
def _normalize_agent_id(raw: str) -> str:
return raw.strip().lower().replace(" ", "-")
def _list_agent_ids(context: Any) -> list[str]:
hook = context.get("list_agent_ids") if isinstance(context, dict) else None
if callable(hook):
out = hook()
if isinstance(out, list):
vals = [str(x) for x in out if isinstance(x, str) and x.strip()]
if vals:
return vals
return [DEFAULT_AGENT_ID]
def _resolve_agent_or_error(raw: Any, context: Any, respond) -> str | None:
requested = _norm_str(raw)
agent_id = _normalize_agent_id(requested) if requested else DEFAULT_AGENT_ID
allowed = set(_list_agent_ids(context))
if agent_id not in allowed:
_bad(respond, f'agent "{agent_id}" not found')
return None
return agent_id
def _agents_list_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 agents.list params")
return
hook = context.get("list_agents_for_gateway") if isinstance(context, dict) else None
if callable(hook):
out = hook()
_ok(respond, out if isinstance(out, dict) else {"agents": []})
return
ids = _list_agent_ids(context)
_ok(respond, {"agents": [{"id": aid, "name": aid} for aid in ids]})
def _agents_create_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 agents.create params")
return
name = _norm_str(params.get("name"))
workspace = _norm_str(params.get("workspace"))
if not name or not workspace:
_bad(respond, "invalid agents.create params")
return
agent_id = _normalize_agent_id(name)
if agent_id == DEFAULT_AGENT_ID:
_bad(respond, f'"{DEFAULT_AGENT_ID}" is reserved')
return
if agent_id in set(_list_agent_ids(context)):
_bad(respond, f'agent "{agent_id}" already exists')
return
hook = context.get("create_agent") if isinstance(context, dict) else None
if callable(hook):
out = hook(dict(params))
_ok(respond, out if isinstance(out, dict) else {"ok": True, "agentId": agent_id})
return
_ok(respond, {"ok": True, "agentId": agent_id, "name": name, "workspace": workspace, "model": _norm_str(params.get("model"))})
def _agents_update_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 agents.update params")
return
agent_id = _resolve_agent_or_error(params.get("agentId"), context, respond)
if not agent_id:
return
hook = context.get("update_agent") if isinstance(context, dict) else None
if callable(hook):
out = hook(dict(params))
_ok(respond, out if isinstance(out, dict) else {"ok": True, "agentId": agent_id})
return
_ok(respond, {"ok": True, "agentId": agent_id})
def _agents_delete_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 agents.delete params")
return
agent_id = _norm_str(params.get("agentId"))
if not agent_id:
_bad(respond, "invalid agents.delete params")
return
agent_id = _normalize_agent_id(agent_id)
if agent_id == DEFAULT_AGENT_ID:
_bad(respond, f'"{DEFAULT_AGENT_ID}" cannot be deleted')
return
if agent_id not in set(_list_agent_ids(context)):
_bad(respond, f'agent "{agent_id}" not found')
return
hook = context.get("delete_agent") if isinstance(context, dict) else None
if callable(hook):
out = hook(dict(params))
_ok(respond, out if isinstance(out, dict) else {"ok": True, "agentId": agent_id, "removedBindings": []})
return
_ok(respond, {"ok": True, "agentId": agent_id, "removedBindings": []})
def _agents_files_list_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 agents.files.list params")
return
agent_id = _resolve_agent_or_error(params.get("agentId"), context, respond)
if not agent_id:
return
hook = context.get("agents_files_list") if isinstance(context, dict) else None
if callable(hook):
out = hook({"agentId": agent_id})
_ok(respond, out if isinstance(out, dict) else {"agentId": agent_id, "workspace": ".", "files": []})
return
files = [{"name": n, "path": f"./{n}", "missing": True} for n in sorted(ALLOWED_FILE_NAMES)]
_ok(respond, {"agentId": agent_id, "workspace": ".", "files": files})
def _agents_files_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 agents.files.get params")
return
agent_id = _resolve_agent_or_error(params.get("agentId"), context, respond)
if not agent_id:
return
name = _norm_str(params.get("name")) or ""
if name not in ALLOWED_FILE_NAMES:
_bad(respond, f'unsupported file "{name}"')
return
hook = context.get("agents_files_get") if isinstance(context, dict) else None
if callable(hook):
out = hook({"agentId": agent_id, "name": name})
_ok(
respond,
out
if isinstance(out, dict)
else {"agentId": agent_id, "workspace": ".", "file": {"name": name, "path": f"./{name}", "missing": True}},
)
return
_ok(respond, {"agentId": agent_id, "workspace": ".", "file": {"name": name, "path": f"./{name}", "missing": True}})
def _agents_files_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 agents.files.set params")
return
agent_id = _resolve_agent_or_error(params.get("agentId"), context, respond)
if not agent_id:
return
name = _norm_str(params.get("name")) or ""
content = params.get("content")
if name not in ALLOWED_FILE_NAMES or not isinstance(content, str):
_bad(respond, "invalid agents.files.set params")
return
hook = context.get("agents_files_set") if isinstance(context, dict) else None
if callable(hook):
out = hook({"agentId": agent_id, "name": name, "content": content})
_ok(
respond,
out
if isinstance(out, dict)
else {
"ok": True,
"agentId": agent_id,
"workspace": ".",
"file": {"name": name, "path": f"./{name}", "missing": False, "content": content},
},
)
return
_ok(
respond,
{
"ok": True,
"agentId": agent_id,
"workspace": ".",
"file": {"name": name, "path": f"./{name}", "missing": False, "content": content},
},
)
agents_handlers: GatewayRequestHandlers = {
"agents.list": _agents_list_handler,
"agents.create": _agents_create_handler,
"agents.update": _agents_update_handler,
"agents.delete": _agents_delete_handler,
"agents.files.list": _agents_files_list_handler,
"agents.files.get": _agents_files_get_handler,
"agents.files.set": _agents_files_set_handler,
}