4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
"""Attach SKILL.md workflow guidance to discovered tools.
|
|
|
|
The registry facade (:mod:`tools.registry`) calls :func:`apply_skill_guidance`
|
|
after collecting tools so a tool's description carries the workflow guidance the
|
|
matching SKILL.md declares for it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import replace
|
|
from pathlib import Path
|
|
|
|
from config.constants.paths import REPO_ROOT
|
|
from core.tool_framework.registered_tool import RegisteredTool
|
|
from core.tool_framework.skill_guidance import format_tool_skill_guidance, load_tool_skill_guidance
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_MAX_TOOL_SKILL_GUIDANCE_CHARS = 2400
|
|
|
|
|
|
def _skill_guidance_files() -> tuple[Path, ...]:
|
|
"""Return explicit and package-local SKILL.md files attached at registry load."""
|
|
|
|
explicit = (REPO_ROOT / "integrations" / "github" / "tools" / "workflow" / "SKILL.md",)
|
|
discovered = sorted(
|
|
(REPO_ROOT / "tools" / "system" / "python_execution_tool" / "skills").glob("*/SKILL.md")
|
|
)
|
|
return (*explicit, *discovered)
|
|
|
|
|
|
def _truncate_skill_guidance(text: str) -> str:
|
|
if len(text) <= _MAX_TOOL_SKILL_GUIDANCE_CHARS:
|
|
return text
|
|
return text[: _MAX_TOOL_SKILL_GUIDANCE_CHARS - 3].rstrip() + "..."
|
|
|
|
|
|
def _with_skill_guidance(tool: RegisteredTool, guidance: str) -> RegisteredTool:
|
|
if not guidance:
|
|
return tool
|
|
return replace(
|
|
tool,
|
|
description=f"{tool.description}\n\nWorkflow guidance:\n{guidance}",
|
|
skill_guidance=guidance,
|
|
)
|
|
|
|
|
|
def apply_skill_guidance(
|
|
tools_by_name: dict[str, RegisteredTool],
|
|
*,
|
|
known_tool_names: frozenset[str] | None = None,
|
|
) -> None:
|
|
# Diagnostics validate against the full tool set (a surface load holds only a
|
|
# subset); guidance still attaches only to tools present in ``tools_by_name``.
|
|
diagnostic_names = (
|
|
known_tool_names if known_tool_names is not None else frozenset(tools_by_name)
|
|
)
|
|
guidance_by_tool: dict[str, list[str]] = {}
|
|
|
|
for skill_path in _skill_guidance_files():
|
|
result = load_tool_skill_guidance(skill_path, known_tool_names=diagnostic_names)
|
|
for diagnostic in result.diagnostics:
|
|
logger.warning(
|
|
"[tools] Skill guidance %s (%s): %s",
|
|
diagnostic.path,
|
|
diagnostic.code,
|
|
diagnostic.message,
|
|
)
|
|
skill = result.skill
|
|
if skill is None or skill.disable_model_invocation:
|
|
continue
|
|
guidance = format_tool_skill_guidance(skill)
|
|
for tool_name in skill.tool_names:
|
|
if tool_name not in tools_by_name:
|
|
continue
|
|
guidance_by_tool.setdefault(tool_name, []).append(guidance)
|
|
|
|
for tool_name, guidances in guidance_by_tool.items():
|
|
combined = _truncate_skill_guidance("\n\n".join(guidances))
|
|
tools_by_name[tool_name] = _with_skill_guidance(tools_by_name[tool_name], combined)
|