mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 00:10:44 +08:00
重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
This commit is contained in:
parent
ba3836f00f
commit
4a23b715a2
498 changed files with 2760 additions and 2200 deletions
273
interfaces/gateway/server_methods/skills.py
Normal file
273
interfaces/gateway/server_methods/skills.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from oclaw.runtime.skill_installer import (
|
||||
create_skill_from_template,
|
||||
install_skill_from_local_dir,
|
||||
install_skill_from_registry_archive,
|
||||
list_skills_with_status,
|
||||
set_skill_enabled,
|
||||
)
|
||||
from oclaw.runtime.skills import discover_workspace_skill_manifests, load_skill_manifest
|
||||
|
||||
from .shared_types import GatewayRequestHandlers
|
||||
from .validation import error_shape
|
||||
|
||||
|
||||
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 _ok(respond, payload: Any) -> None:
|
||||
if callable(respond):
|
||||
respond(True, payload, None, None)
|
||||
|
||||
|
||||
def _normalize_optional_str(value: Any) -> str | None:
|
||||
if isinstance(value, str):
|
||||
v = value.strip()
|
||||
return v or None
|
||||
return None
|
||||
|
||||
|
||||
def _get_store(context: Any) -> Any | None:
|
||||
if isinstance(context, dict) and context.get("store") is not None:
|
||||
return context.get("store")
|
||||
return None
|
||||
|
||||
|
||||
def _skills_status_handler(opts: dict[str, Any]) -> None:
|
||||
respond = opts.get("respond")
|
||||
context = opts.get("context")
|
||||
store = _get_store(context)
|
||||
if store is None:
|
||||
_unavailable(respond, "skills.status requires context.store")
|
||||
return
|
||||
_ok(respond, {"skills": list_skills_with_status(store=store)})
|
||||
|
||||
|
||||
def _skills_bins_handler(opts: dict[str, Any]) -> None:
|
||||
respond = opts.get("respond")
|
||||
bins: set[str] = set()
|
||||
for m in discover_workspace_skill_manifests():
|
||||
oc = dict(m.metadata_oclaw or {})
|
||||
req = oc.get("requires")
|
||||
if isinstance(req, dict):
|
||||
for k in ("bins", "anyBins"):
|
||||
raw = req.get(k)
|
||||
if isinstance(raw, list):
|
||||
for it in raw:
|
||||
v = _normalize_optional_str(it)
|
||||
if v:
|
||||
bins.add(v)
|
||||
for spec in m.install:
|
||||
payload = dict(spec.payload or {})
|
||||
raw_bins = payload.get("bins")
|
||||
if isinstance(raw_bins, list):
|
||||
for it in raw_bins:
|
||||
v = _normalize_optional_str(it)
|
||||
if v:
|
||||
bins.add(v)
|
||||
_ok(respond, {"bins": sorted(bins, key=lambda x: x.lower())})
|
||||
|
||||
|
||||
def _skills_search_handler(opts: dict[str, Any]) -> None:
|
||||
respond = opts.get("respond")
|
||||
params = opts.get("params")
|
||||
context = opts.get("context")
|
||||
if not isinstance(params, dict):
|
||||
_bad(respond, "invalid skills.search params")
|
||||
return
|
||||
query = _normalize_optional_str(params.get("query")) or ""
|
||||
limit = params.get("limit")
|
||||
limit_n = int(limit) if isinstance(limit, int) and limit > 0 else 20
|
||||
hook = context.get("search_clawhub_skills") if isinstance(context, dict) else None
|
||||
if callable(hook):
|
||||
try:
|
||||
results = hook({"query": query, "limit": limit_n})
|
||||
_ok(respond, {"results": results if isinstance(results, list) else []})
|
||||
except Exception as exc:
|
||||
_unavailable(respond, str(exc))
|
||||
return
|
||||
out: list[dict[str, Any]] = []
|
||||
for m in discover_workspace_skill_manifests():
|
||||
hay = f"{m.name}\n{m.description}\n{m.body}".lower()
|
||||
if query.lower() in hay:
|
||||
out.append({"slug": m.name, "name": m.name, "description": m.description, "source": "local"})
|
||||
if len(out) >= limit_n:
|
||||
break
|
||||
_ok(respond, {"results": out})
|
||||
|
||||
|
||||
def _skills_detail_handler(opts: dict[str, Any]) -> None:
|
||||
respond = opts.get("respond")
|
||||
params = opts.get("params")
|
||||
context = opts.get("context")
|
||||
if not isinstance(params, dict):
|
||||
_bad(respond, "invalid skills.detail params")
|
||||
return
|
||||
slug = _normalize_optional_str(params.get("slug"))
|
||||
if not slug:
|
||||
_bad(respond, "invalid skills.detail params: slug required")
|
||||
return
|
||||
hook = context.get("fetch_clawhub_skill_detail") if isinstance(context, dict) else None
|
||||
if callable(hook):
|
||||
try:
|
||||
detail = hook({"slug": slug})
|
||||
_ok(respond, detail if isinstance(detail, dict) else {"slug": slug})
|
||||
except Exception as exc:
|
||||
_unavailable(respond, str(exc))
|
||||
return
|
||||
manifest = load_skill_manifest(slug)
|
||||
if manifest is None:
|
||||
for m in discover_workspace_skill_manifests():
|
||||
if m.name == slug:
|
||||
manifest = m
|
||||
break
|
||||
if manifest is None:
|
||||
_bad(respond, f"unknown skill slug: {slug}")
|
||||
return
|
||||
_ok(
|
||||
respond,
|
||||
{
|
||||
"slug": manifest.name,
|
||||
"name": manifest.name,
|
||||
"description": manifest.description,
|
||||
"skillDir": manifest.skill_dir,
|
||||
"skillFile": manifest.skill_file,
|
||||
"metadata": {"oclaw": dict(manifest.metadata_oclaw)},
|
||||
"body": manifest.body,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _skills_install_handler(opts: dict[str, Any]) -> None:
|
||||
respond = opts.get("respond")
|
||||
params = opts.get("params")
|
||||
context = opts.get("context")
|
||||
store = _get_store(context)
|
||||
if store is None:
|
||||
_unavailable(respond, "skills.install requires context.store")
|
||||
return
|
||||
if not isinstance(params, dict):
|
||||
_bad(respond, "invalid skills.install params")
|
||||
return
|
||||
source = _normalize_optional_str(params.get("source")) or "local"
|
||||
if source == "clawhub":
|
||||
hook = context.get("install_skill_from_clawhub") if isinstance(context, dict) else None
|
||||
if not callable(hook):
|
||||
_unavailable(respond, "clawhub install is not configured")
|
||||
return
|
||||
try:
|
||||
result = hook(dict(params))
|
||||
_ok(respond, result)
|
||||
except Exception as exc:
|
||||
_unavailable(respond, str(exc))
|
||||
return
|
||||
|
||||
overwrite = bool(params.get("force"))
|
||||
archive_url = _normalize_optional_str(params.get("archiveUrl"))
|
||||
source_dir = _normalize_optional_str(params.get("sourceDir"))
|
||||
if archive_url:
|
||||
res = install_skill_from_registry_archive(store=store, archive_url=archive_url, overwrite=overwrite)
|
||||
_ok(respond, {"ok": res.ok, "result": res.__dict__})
|
||||
return
|
||||
if source_dir:
|
||||
res = install_skill_from_local_dir(store=store, source_dir=source_dir, overwrite=overwrite)
|
||||
_ok(respond, {"ok": res.ok, "result": res.__dict__})
|
||||
return
|
||||
|
||||
name = _normalize_optional_str(params.get("name"))
|
||||
if not name:
|
||||
_bad(respond, "invalid skills.install params: name required (or archiveUrl/sourceDir)")
|
||||
return
|
||||
description = _normalize_optional_str(params.get("description")) or f"{name} skill"
|
||||
body = _normalize_optional_str(params.get("body")) or ""
|
||||
md = params.get("metadata_oclaw")
|
||||
md = dict(md) if isinstance(md, dict) else {}
|
||||
res = create_skill_from_template(
|
||||
store=store,
|
||||
name=name,
|
||||
description=description,
|
||||
body_markdown=body,
|
||||
metadata_oclaw=md,
|
||||
overwrite=overwrite,
|
||||
)
|
||||
_ok(respond, {"ok": res.ok, "result": res.__dict__})
|
||||
|
||||
|
||||
def _skills_update_handler(opts: dict[str, Any]) -> None:
|
||||
respond = opts.get("respond")
|
||||
params = opts.get("params")
|
||||
context = opts.get("context")
|
||||
store = _get_store(context)
|
||||
if store is None:
|
||||
_unavailable(respond, "skills.update requires context.store")
|
||||
return
|
||||
if not isinstance(params, dict):
|
||||
_bad(respond, "invalid skills.update params")
|
||||
return
|
||||
source = _normalize_optional_str(params.get("source"))
|
||||
if source == "clawhub":
|
||||
hook = context.get("update_skills_from_clawhub") if isinstance(context, dict) else None
|
||||
if not callable(hook):
|
||||
_unavailable(respond, "clawhub update is not configured")
|
||||
return
|
||||
slug = _normalize_optional_str(params.get("slug"))
|
||||
all_flag = bool(params.get("all"))
|
||||
if not slug and not all_flag:
|
||||
_bad(respond, 'clawhub skills.update requires "slug" or "all"')
|
||||
return
|
||||
if slug and all_flag:
|
||||
_bad(respond, 'clawhub skills.update accepts either "slug" or "all", not both')
|
||||
return
|
||||
try:
|
||||
result = hook(dict(params))
|
||||
_ok(respond, result)
|
||||
except Exception as exc:
|
||||
_unavailable(respond, str(exc))
|
||||
return
|
||||
|
||||
skill_key = _normalize_optional_str(params.get("skillKey"))
|
||||
if not skill_key:
|
||||
_bad(respond, "invalid skills.update params: skillKey required")
|
||||
return
|
||||
if isinstance(params.get("enabled"), bool):
|
||||
set_skill_enabled(store=store, skill_name=skill_key, enabled=bool(params["enabled"]))
|
||||
|
||||
api_key = params.get("apiKey")
|
||||
if isinstance(api_key, str):
|
||||
trimmed = api_key.strip()
|
||||
store.set_setting(f"SKILL_API_KEY:{skill_key}", trimmed)
|
||||
env = params.get("env")
|
||||
if isinstance(env, dict):
|
||||
clean: dict[str, str] = {}
|
||||
for k, v in env.items():
|
||||
kk = _normalize_optional_str(k)
|
||||
vv = _normalize_optional_str(v)
|
||||
if not kk:
|
||||
continue
|
||||
if vv is None:
|
||||
continue
|
||||
clean[kk] = vv
|
||||
store.set_setting(f"SKILL_ENV:{skill_key}", json.dumps(clean, ensure_ascii=False))
|
||||
|
||||
_ok(respond, {"ok": True, "skillKey": skill_key})
|
||||
|
||||
|
||||
skills_handlers: GatewayRequestHandlers = {
|
||||
"skills.status": _skills_status_handler,
|
||||
"skills.bins": _skills_bins_handler,
|
||||
"skills.search": _skills_search_handler,
|
||||
"skills.detail": _skills_detail_handler,
|
||||
"skills.install": _skills_install_handler,
|
||||
"skills.update": _skills_update_handler,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue