Add skill enable controls and data factory SQL skill
This commit is contained in:
+107
-1
@@ -34,7 +34,7 @@ from src.agent_types import (
|
||||
AgentRuntimeConfig,
|
||||
ModelConfig,
|
||||
)
|
||||
from src.bundled_skills import get_bundled_skills
|
||||
from src.bundled_skills import ALWAYS_ENABLED_HIDDEN_SKILL_NAMES, get_bundled_skills
|
||||
from src.session_store import (
|
||||
DEFAULT_AGENT_SESSION_DIR,
|
||||
StoredAgentSession,
|
||||
@@ -247,6 +247,9 @@ class AgentInstanceConfig:
|
||||
allow_write: bool
|
||||
|
||||
|
||||
SKILL_SETTINGS_FILENAME = 'skill_settings.json'
|
||||
|
||||
|
||||
class RunProcessRegistry:
|
||||
def __init__(self) -> None:
|
||||
self._lock = Lock()
|
||||
@@ -518,6 +521,90 @@ class AgentState:
|
||||
'python_env': base / 'python' / '.venv',
|
||||
}
|
||||
|
||||
def _skill_settings_path(self, account_id: str | None) -> Path:
|
||||
return self.account_paths(account_id)['base'] / SKILL_SETTINGS_FILENAME
|
||||
|
||||
def _load_disabled_skill_names(self, account_id: str | None) -> set[str]:
|
||||
path = self._skill_settings_path(account_id)
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding='utf-8'))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return set()
|
||||
disabled = payload.get('disabled_skill_names')
|
||||
if not isinstance(disabled, list):
|
||||
return set()
|
||||
return {
|
||||
str(name).strip().lower()
|
||||
for name in disabled
|
||||
if str(name).strip()
|
||||
}
|
||||
|
||||
def _save_disabled_skill_names(
|
||||
self,
|
||||
account_id: str | None,
|
||||
disabled_skill_names: set[str],
|
||||
) -> None:
|
||||
path = self._skill_settings_path(account_id)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
payload = {
|
||||
'disabled_skill_names': sorted(
|
||||
name
|
||||
for name in disabled_skill_names
|
||||
if name not in ALWAYS_ENABLED_HIDDEN_SKILL_NAMES
|
||||
)
|
||||
}
|
||||
path.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + '\n',
|
||||
encoding='utf-8',
|
||||
)
|
||||
|
||||
def enabled_skill_names(self, account_id: str | None) -> tuple[str, ...]:
|
||||
config = self._config_for(account_id)
|
||||
disabled = self._load_disabled_skill_names(account_id)
|
||||
enabled: list[str] = []
|
||||
for skill in get_bundled_skills(config.cwd):
|
||||
if not skill.user_invocable:
|
||||
continue
|
||||
lowered = skill.name.lower()
|
||||
if (
|
||||
lowered in ALWAYS_ENABLED_HIDDEN_SKILL_NAMES
|
||||
or lowered not in disabled
|
||||
):
|
||||
enabled.append(skill.name)
|
||||
return tuple(enabled)
|
||||
|
||||
def set_skill_enabled(
|
||||
self,
|
||||
account_id: str | None,
|
||||
skill_name: str,
|
||||
enabled: bool,
|
||||
) -> None:
|
||||
with self._lock:
|
||||
skill_name = skill_name.strip()
|
||||
if not skill_name:
|
||||
raise ValueError('skill name must not be empty')
|
||||
lowered = skill_name.lower()
|
||||
if lowered in ALWAYS_ENABLED_HIDDEN_SKILL_NAMES:
|
||||
raise ValueError(
|
||||
f'skill is always enabled and cannot be changed: {skill_name}'
|
||||
)
|
||||
config = self._config_for(account_id)
|
||||
if not any(
|
||||
skill.name.lower() == lowered and skill.user_invocable
|
||||
for skill in get_bundled_skills(config.cwd)
|
||||
):
|
||||
raise ValueError(f'unknown skill: {skill_name}')
|
||||
disabled = self._load_disabled_skill_names(account_id)
|
||||
if enabled:
|
||||
disabled.discard(lowered)
|
||||
else:
|
||||
disabled.add(lowered)
|
||||
self._save_disabled_skill_names(account_id, disabled)
|
||||
account_prefix = f'{self._account_key(account_id)}:'
|
||||
for key in list(self._agents):
|
||||
if key.startswith(account_prefix):
|
||||
self._agents.pop(key, None)
|
||||
|
||||
def _build_agent(self, account_id: str | None = None) -> LocalCodingAgent:
|
||||
config = self._config_for(account_id)
|
||||
paths = self.account_paths(account_id)
|
||||
@@ -536,6 +623,7 @@ class AgentState:
|
||||
session_directory=paths['sessions'],
|
||||
scratchpad_root=paths['scratchpad'],
|
||||
python_env_dir=paths['python_env'],
|
||||
enabled_skill_names=self.enabled_skill_names(account_id),
|
||||
)
|
||||
model_config = ModelConfig(
|
||||
model=config.model,
|
||||
@@ -676,6 +764,12 @@ class ModelListRequest(BaseModel):
|
||||
account_id: str | None = None
|
||||
|
||||
|
||||
class SkillPreferenceUpdate(BaseModel):
|
||||
skill: str = Field(min_length=1)
|
||||
enabled: bool
|
||||
account_id: str | None = None
|
||||
|
||||
|
||||
class SessionTitleUpdate(BaseModel):
|
||||
title: str = Field(min_length=1, max_length=80)
|
||||
|
||||
@@ -734,6 +828,7 @@ def create_app(state: AgentState) -> FastAPI:
|
||||
@app.get('/api/skills')
|
||||
async def list_skills(account_id: str | None = None) -> list[dict[str, Any]]:
|
||||
config = state.config_for(account_id)
|
||||
disabled = state._load_disabled_skill_names(account_id)
|
||||
return [
|
||||
{
|
||||
'name': skill.name,
|
||||
@@ -741,11 +836,22 @@ def create_app(state: AgentState) -> FastAPI:
|
||||
'when_to_use': skill.when_to_use,
|
||||
'aliases': list(skill.aliases),
|
||||
'allowed_tools': list(skill.allowed_tools),
|
||||
'enabled': skill.name.lower() not in disabled,
|
||||
'configurable': skill.name.lower() not in ALWAYS_ENABLED_HIDDEN_SKILL_NAMES,
|
||||
}
|
||||
for skill in get_bundled_skills(config.cwd)
|
||||
if skill.user_invocable
|
||||
and skill.name.lower() not in ALWAYS_ENABLED_HIDDEN_SKILL_NAMES
|
||||
]
|
||||
|
||||
@app.patch('/api/skills')
|
||||
async def update_skill_preference(payload: SkillPreferenceUpdate) -> list[dict[str, Any]]:
|
||||
try:
|
||||
state.set_skill_enabled(payload.account_id, payload.skill, payload.enabled)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
return await list_skills(payload.account_id)
|
||||
|
||||
@app.get('/api/models')
|
||||
async def list_models_get(account_id: str | None = None) -> dict[str, Any]:
|
||||
config = state.config_for(account_id)
|
||||
|
||||
Reference in New Issue
Block a user