mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 01:50:44 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
|
|
@ -11,6 +11,7 @@ from dataclasses import dataclass
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from oclaw.runtime.skills_market import get_market_adapter
|
||||
from oclaw.runtime.skills import (
|
||||
default_skills_root,
|
||||
discover_workspace_skill_manifests,
|
||||
|
|
@ -45,7 +46,7 @@ def _classify_install_detail(detail: str) -> tuple[str, bool]:
|
|||
return ("invalid_skill_package", False)
|
||||
if d.startswith("rollback_after_error"):
|
||||
return ("runtime_error", True)
|
||||
if d == "installed" or d == "created":
|
||||
if d in {"installed", "created", "removed"}:
|
||||
return ("ok", False)
|
||||
return ("unknown", False)
|
||||
|
||||
|
|
@ -118,6 +119,32 @@ def set_skill_enabled(*, store: Any, skill_name: str, enabled: bool) -> None:
|
|||
_set_disabled_names(store, disabled)
|
||||
|
||||
|
||||
def uninstall_skill(
|
||||
*,
|
||||
store: Any,
|
||||
skill_name: str,
|
||||
skills_root: str | Path | None = None,
|
||||
) -> SkillInstallResult:
|
||||
name = str(skill_name or "").strip()
|
||||
if not name:
|
||||
ec, rt = _classify_install_detail("name_required")
|
||||
return SkillInstallResult(ok=False, name="", target_dir="", detail="name_required", error_code=ec, retryable=rt)
|
||||
root = Path(skills_root).resolve() if skills_root else default_skills_root()
|
||||
candidates = [root / name, root / "_workspace" / name]
|
||||
target = next((p for p in candidates if p.exists() and p.is_dir()), None)
|
||||
if target is None:
|
||||
ec, rt = _classify_install_detail("not_found")
|
||||
return SkillInstallResult(ok=False, name=name, target_dir="", detail="not_found", error_code=ec, retryable=rt)
|
||||
try:
|
||||
shutil.rmtree(target)
|
||||
except Exception as exc:
|
||||
ec, rt = _classify_install_detail("runtime_error")
|
||||
return SkillInstallResult(ok=False, name=name, target_dir=str(target), detail=f"remove_failed:{type(exc).__name__}", error_code=ec, retryable=rt)
|
||||
set_skill_enabled(store=store, skill_name=name, enabled=True)
|
||||
ec, rt = _classify_install_detail("removed")
|
||||
return SkillInstallResult(ok=True, name=name, target_dir=str(target), detail="removed", error_code=ec, retryable=rt)
|
||||
|
||||
|
||||
def scan_skill_source_dir(src_dir: str | Path) -> tuple[bool, str]:
|
||||
src = Path(src_dir).resolve()
|
||||
if not src.exists() or not src.is_dir():
|
||||
|
|
@ -179,17 +206,29 @@ def _resolve_registry_archive_url(url: str) -> str:
|
|||
segs = [s for s in str(parsed.path or "").split("/") if s]
|
||||
if not segs:
|
||||
return raw
|
||||
# Clawhub page URLs: https://clawhub.ai/<author>/<slug>
|
||||
# Resolve via clawhub client first (matches test + avoids adapter drift).
|
||||
try:
|
||||
if len(segs) >= 2:
|
||||
from oclaw.runtime.tools.skills.clawhub_client import get_skill_detail
|
||||
|
||||
slug = str(segs[-1] or "").strip()
|
||||
if slug:
|
||||
detail = get_skill_detail(slug)
|
||||
archive_url = str((detail or {}).get("archiveUrl") or "").strip()
|
||||
if archive_url:
|
||||
return archive_url
|
||||
except Exception:
|
||||
pass
|
||||
candidates: list[str] = []
|
||||
if len(segs) >= 1:
|
||||
candidates.append(segs[-1])
|
||||
if len(segs) >= 2:
|
||||
candidates.append(f"{segs[0]}/{segs[1]}")
|
||||
try:
|
||||
from oclaw.runtime.tools.skills.clawhub_client import get_skill_detail
|
||||
|
||||
adapter = get_market_adapter("clawhub")
|
||||
for slug in candidates:
|
||||
detail = get_skill_detail(slug)
|
||||
archive_url = str(detail.get("archiveUrl") or "").strip() if isinstance(detail, dict) else ""
|
||||
archive_url, _ = adapter.resolve_archive_url(slug=slug, version=None)
|
||||
if archive_url:
|
||||
return archive_url
|
||||
return raw
|
||||
|
|
@ -342,6 +381,68 @@ def create_skill_from_template(
|
|||
return SkillInstallResult(ok=True, name=nm, target_dir=str(target), detail="created", error_code=ec, retryable=rt)
|
||||
|
||||
|
||||
def create_workspace_skill(
|
||||
*,
|
||||
store: Any,
|
||||
name: str,
|
||||
description: str,
|
||||
runtime_type: str = "python",
|
||||
skills_root: str | Path | None = None,
|
||||
overwrite: bool = False,
|
||||
) -> SkillInstallResult:
|
||||
nm = str(name or "").strip()
|
||||
if not nm:
|
||||
ec, rt = _classify_install_detail("name_required")
|
||||
return SkillInstallResult(ok=False, name="", target_dir="", detail="name_required", error_code=ec, retryable=rt)
|
||||
rt_type = str(runtime_type or "python").strip().lower()
|
||||
if rt_type not in {"python", "shell", "node"}:
|
||||
ec, rt = _classify_install_detail("unsupported_runtime_type")
|
||||
return SkillInstallResult(ok=False, name=nm, target_dir="", detail="unsupported_runtime_type", error_code=ec, retryable=rt)
|
||||
root = Path(skills_root).resolve() if skills_root else default_skills_root()
|
||||
target = root / "_workspace" / nm
|
||||
if target.exists() and not overwrite:
|
||||
ec, rt = _classify_install_detail("already_exists")
|
||||
return SkillInstallResult(ok=False, name=nm, target_dir=str(target), detail="already_exists", error_code=ec, retryable=rt)
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
scripts_dir = target / "scripts"
|
||||
scripts_dir.mkdir(parents=True, exist_ok=True)
|
||||
entry = "scripts/run.py" if rt_type == "python" else ("scripts/run.sh" if rt_type == "shell" else "scripts/run.js")
|
||||
script_path = target / entry
|
||||
if rt_type == "python":
|
||||
script_path.write_text(
|
||||
"import json\nimport sys\n\n"
|
||||
"def main():\n"
|
||||
" data = json.loads(sys.stdin.read() or '{}')\n"
|
||||
" args = data.get('args') or {}\n"
|
||||
" print(json.dumps({'ok': True, 'echo': args}, ensure_ascii=False))\n\n"
|
||||
"if __name__ == '__main__':\n"
|
||||
" main()\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
elif rt_type == "shell":
|
||||
script_path.write_text("#!/usr/bin/env bash\ncat\n", encoding="utf-8")
|
||||
else:
|
||||
script_path.write_text("const fs=require('fs'); const input=JSON.parse(fs.readFileSync(0,'utf8')||'{}'); console.log(JSON.stringify({ok:true,echo:input.args||{}}));\n", encoding="utf-8")
|
||||
metadata_oclaw = {
|
||||
"source": {"kind": "workspace", "provider": "local", "version": "workspace"},
|
||||
"runtime": {
|
||||
"type": rt_type,
|
||||
"entry": entry,
|
||||
"schema": {"type": "object", "additionalProperties": True},
|
||||
"permissions": {"fs_write": False, "net": False, "process": True},
|
||||
},
|
||||
}
|
||||
return create_skill_from_template(
|
||||
store=store,
|
||||
name=nm,
|
||||
description=description,
|
||||
body_markdown="Workspace authored skill.",
|
||||
metadata_oclaw=metadata_oclaw,
|
||||
skills_root=target.parent,
|
||||
overwrite=True,
|
||||
)
|
||||
|
||||
|
||||
def auto_install_skill_from_payload(
|
||||
*,
|
||||
store: Any,
|
||||
|
|
@ -393,9 +494,11 @@ __all__ = [
|
|||
"SkillInstallResult",
|
||||
"auto_install_skill_from_payload",
|
||||
"create_skill_from_template",
|
||||
"create_workspace_skill",
|
||||
"install_skill_from_local_dir",
|
||||
"install_skill_from_registry_archive",
|
||||
"list_skills_with_status",
|
||||
"set_skill_enabled",
|
||||
"skill_auto_install_enabled",
|
||||
"uninstall_skill",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue