Phase 1: Permission modal dialog, session resume modal, HistoryInput with up/down arrow cycling, remove "You:" echo from chat log, LLM client cleanup on unmount. Phase 2: Smart shell auto-approve using allowed/denied command lists from ToolsConfig, animated thinking spinner with live token count in status bar. Phase 3: /models slash command (list + switch), CLI directory positional argument, JSONL debug logger with rotation. Phase 4: Skills system with SkillsManager, load_skill LLM tool, /skills listing, skill invocation via slash commands, system prompt integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 lines
1.8 KiB
Python
61 lines
1.8 KiB
Python
"""Load skill tool — allows the LLM to load skill instructions on demand."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any, ClassVar
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.models.config import AppConfig
|
|
from app.models.tool_call import ToolResult, ToolResultStatus
|
|
from app.services.skills import SkillsManager
|
|
from app.tools.base import BaseTool
|
|
|
|
|
|
class LoadSkillParams(BaseModel):
|
|
"""Parameters for the load_skill tool."""
|
|
|
|
name: str = Field(description="Name of the skill to load")
|
|
|
|
|
|
class LoadSkillTool(BaseTool):
|
|
"""Load a skill's full instructions by name.
|
|
|
|
Use when a skill is relevant to the current task.
|
|
"""
|
|
|
|
name: ClassVar[str] = "load_skill"
|
|
description: ClassVar[str] = (
|
|
"Load a skill's full instructions by name. "
|
|
"Use when a skill is relevant to the current task."
|
|
)
|
|
params_model: ClassVar[type[BaseModel]] = LoadSkillParams
|
|
|
|
def __init__(
|
|
self,
|
|
workspace_root: Path,
|
|
config: AppConfig,
|
|
skills_manager: SkillsManager,
|
|
) -> None:
|
|
super().__init__(workspace_root, config)
|
|
self._skills = skills_manager
|
|
|
|
def execute(self, *, tool_call_id: str, **kwargs: Any) -> ToolResult:
|
|
skill_name: str = kwargs["name"]
|
|
content = self._skills.load_skill(skill_name)
|
|
if content is None:
|
|
available = [s.name for s in self._skills.list_skills()]
|
|
return ToolResult(
|
|
tool_call_id=tool_call_id,
|
|
tool_name=self.name,
|
|
status=ToolResultStatus.ERROR,
|
|
error=f"Unknown skill '{skill_name}'. Available: {available}",
|
|
)
|
|
return ToolResult(
|
|
tool_call_id=tool_call_id,
|
|
tool_name=self.name,
|
|
status=ToolResultStatus.SUCCESS,
|
|
output=content,
|
|
)
|