统一技能安装链路并提交当前skills变更。

新增 public 的 skill_auto_install 工具并移除 catalog 旧实现,同时按当前工作区状态提交 skills 精简与 skill-creator 新增内容。

Made-with: Cursor
This commit is contained in:
oliver 2026-05-01 04:23:24 +08:00
parent 1c71a29ec1
commit 329fb2239a
45 changed files with 1291 additions and 6100 deletions

View file

@ -120,134 +120,6 @@ def _resolve_tool_conflicts(collected: list[tuple[str, ToolSpec]]) -> list[ToolS
return output
def _skill_management_tools(store: SqliteStore) -> list[ToolSpec]:
# Lazy imports to avoid circular dependency at module import time.
from oclaw.runtime.skill_installer import (
auto_install_skill_from_payload,
create_skill_from_template,
install_skill_from_registry_archive,
list_skills_with_status,
)
from oclaw.runtime.skills import default_skills_root
from oclaw.runtime.skills_market import get_market_adapter, normalize_skill_market_provider_setting
def _create_skill_handler(args: dict[str, Any]) -> dict[str, Any]:
out = create_skill_from_template(
store=store,
name=str(args.get("name") or "").strip(),
description=str(args.get("description") or "").strip(),
body_markdown=str(args.get("body_markdown") or "").strip(),
metadata_oclaw=dict(args.get("metadata_oclaw") or {}) if isinstance(args.get("metadata_oclaw"), dict) else {},
overwrite=bool(args.get("overwrite")),
)
return {"ok": bool(out.ok), "name": out.name, "target_dir": out.target_dir, "detail": out.detail}
def _auto_install_skill_handler(args: dict[str, Any]) -> dict[str, Any]:
archive_url = str(args.get("archive_url") or "").strip()
slug = str(args.get("slug") or "").strip()
version = str(args.get("version") or "").strip() or None
overwrite = bool(args.get("overwrite"))
provider = normalize_skill_market_provider_setting(
str(args.get("provider") or store.get_setting("AIA_SKILL_MARKET_PROVIDER") or "")
)
if not archive_url and slug:
try:
adapter = get_market_adapter(provider)
archive_url, _chosen_version = adapter.resolve_archive_url(slug=slug, version=version)
except Exception:
archive_url = ""
if archive_url:
out = install_skill_from_registry_archive(
store=store,
archive_url=archive_url,
overwrite=overwrite,
skills_root=default_skills_root() / "_workspace",
auto_bind=True,
)
return {
"ok": bool(out.ok),
"name": out.name,
"target_dir": out.target_dir,
"detail": out.detail,
"error_code": out.error_code,
"retryable": bool(out.retryable),
"auto_enabled": bool(getattr(out, "auto_enabled", False)),
"binding_applied_roles": list(getattr(out, "binding_applied_roles", ()) or []),
"provider": provider,
}
payload = {
"name": str(args.get("name") or "").strip(),
"description": str(args.get("description") or "").strip(),
"body_markdown": str(args.get("body_markdown") or "").strip(),
"metadata_oclaw": dict(args.get("metadata_oclaw") or {}) if isinstance(args.get("metadata_oclaw"), dict) else {},
}
if not payload["name"]:
return {"ok": False, "error_code": "name_required", "error": "name_required"}
if not payload["description"]:
payload["description"] = f"{payload['name']} skill"
out = auto_install_skill_from_payload(store=store, payload=payload)
return {
"ok": bool(out.ok),
"name": out.name,
"target_dir": out.target_dir,
"detail": out.detail,
"error_code": out.error_code,
"retryable": bool(out.retryable),
"auto_enabled": bool(getattr(out, "auto_enabled", False)),
"binding_applied_roles": list(getattr(out, "binding_applied_roles", ()) or []),
}
def _list_skills_handler(args: dict[str, Any]) -> dict[str, Any]:
del args
return {"ok": True, "items": list_skills_with_status(store=store)}
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"body_markdown": {"type": "string"},
"metadata_oclaw": {"type": "object"},
"slug": {"type": "string"},
"provider": {"type": "string"},
"version": {"type": "string"},
"archive_url": {"type": "string"},
"overwrite": {"type": "boolean"},
},
"required": [],
}
return [
ToolSpec(
name="skill_create",
description="Create a local oclaw skill package from template.",
parameters=schema,
handler=_create_skill_handler,
tags=frozenset({"skill", "oclaw", "builder"}),
risk_level="high",
timeout_s=20.0,
),
ToolSpec(
name="skill_auto_install",
description="Auto install a generated oclaw skill package.",
parameters=schema,
handler=_auto_install_skill_handler,
tags=frozenset({"skill", "oclaw", "installer"}),
risk_level="high",
timeout_s=120.0,
),
ToolSpec(
name="skill_list",
description="List installed skills and status.",
parameters={"type": "object", "properties": {}, "additionalProperties": False},
handler=_list_skills_handler,
tags=frozenset({"skill", "oclaw", "read"}),
read_only=True,
timeout_s=10.0,
),
]
def materialize_tool_specs(
factories: tuple[ToolFactory, ...] | None = None,
*,

View file

@ -0,0 +1,109 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
from oclaw.platform.config.paths import db_path
from oclaw.platform.persistence.sqlite_store import SqliteStore
from oclaw.runtime.skill_installer import auto_install_skill_from_payload, install_skill_from_registry_archive
from oclaw.runtime.skills import default_skills_root
from oclaw.runtime.skills_market import get_market_adapter, normalize_skill_market_provider_setting
from oclaw.runtime.tools.base import ToolSpec
def _store() -> SqliteStore:
return SqliteStore(db_path())
def _agent_workspace_skills_root() -> Path:
return default_skills_root() / "_workspace"
def skill_auto_install_tool() -> ToolSpec:
def _handler(args: dict[str, Any]) -> dict[str, Any]:
payload = args if isinstance(args, dict) else {}
store = _store()
archive_url = str(payload.get("archive_url") or "").strip()
slug = str(payload.get("slug") or "").strip()
version = str(payload.get("version") or "").strip() or None
overwrite = bool(payload.get("overwrite"))
provider = normalize_skill_market_provider_setting(
str(payload.get("provider") or store.get_setting("AIA_SKILL_MARKET_PROVIDER") or "")
)
if not archive_url and slug:
try:
adapter = get_market_adapter(provider)
archive_url, _chosen_version = adapter.resolve_archive_url(slug=slug, version=version)
except Exception:
archive_url = ""
if archive_url:
out = install_skill_from_registry_archive(
store=store,
archive_url=archive_url,
overwrite=overwrite,
skills_root=_agent_workspace_skills_root(),
auto_bind=True,
)
return {
"ok": bool(out.ok),
"name": out.name,
"target_dir": out.target_dir,
"detail": out.detail,
"error_code": out.error_code,
"retryable": bool(out.retryable),
"auto_enabled": bool(getattr(out, "auto_enabled", False)),
"binding_applied_roles": list(getattr(out, "binding_applied_roles", ()) or []),
"provider": provider,
}
skill_payload = {
"name": str(payload.get("name") or "").strip(),
"description": str(payload.get("description") or "").strip(),
"body_markdown": str(payload.get("body_markdown") or "").strip(),
"metadata_oclaw": dict(payload.get("metadata_oclaw") or {})
if isinstance(payload.get("metadata_oclaw"), dict)
else {},
}
if not skill_payload["name"]:
return {"ok": False, "error_code": "name_required", "error": "name_required"}
if not skill_payload["description"]:
skill_payload["description"] = f"{skill_payload['name']} skill"
out = auto_install_skill_from_payload(store=store, payload=skill_payload)
return {
"ok": bool(out.ok),
"name": out.name,
"target_dir": out.target_dir,
"detail": out.detail,
"error_code": out.error_code,
"retryable": bool(out.retryable),
"auto_enabled": bool(getattr(out, "auto_enabled", False)),
"binding_applied_roles": list(getattr(out, "binding_applied_roles", ()) or []),
}
return ToolSpec(
name="skill_auto_install",
description="Auto install a skill into _workspace lane (payload or market/archive).",
parameters={
"type": "object",
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"body_markdown": {"type": "string"},
"metadata_oclaw": {"type": "object"},
"slug": {"type": "string"},
"provider": {"type": "string"},
"version": {"type": "string"},
"archive_url": {"type": "string"},
"overwrite": {"type": "boolean"},
},
"required": [],
"additionalProperties": False,
},
handler=_handler,
tags=frozenset({"skill", "oclaw", "installer"}),
risk_level="high",
timeout_s=120.0,
)
__all__ = ["skill_auto_install_tool"]