diff --git a/offline-backend-20260512/backend/app/gateway/routers/skills.py b/offline-backend-20260512/backend/app/gateway/routers/skills.py index c2736e31..2d1b4122 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/skills.py +++ b/offline-backend-20260512/backend/app/gateway/routers/skills.py @@ -608,21 +608,26 @@ class CuratorLogsResponse(BaseModel): last_run_at: str | None +def _build_curator_config_response(config: AppConfig) -> CuratorConfigResponse: + """Pure helper so both the GET route and the post-PUT refresh share one shape.""" + evo = config.skill_evolution + return CuratorConfigResponse( + enabled=evo.enabled, + moderation_model_name=evo.moderation_model_name, + curator=CuratorSubConfig( + enabled=evo.curator.enabled, + interval_hours=evo.curator.interval_hours, + stale_after_days=evo.curator.stale_after_days, + archive_after_days=evo.curator.archive_after_days, + ), + ) + + @router.get("/curator/config", response_model=CuratorConfigResponse, summary="Get Skill Evolution Config") async def get_curator_config(config: AppConfig = Depends(get_config)) -> CuratorConfigResponse: """Return the current effective skill_evolution config (yaml defaults + skill_evolution.json overrides).""" try: - evo = config.skill_evolution - return CuratorConfigResponse( - enabled=evo.enabled, - moderation_model_name=evo.moderation_model_name, - curator=CuratorSubConfig( - enabled=evo.curator.enabled, - interval_hours=evo.curator.interval_hours, - stale_after_days=evo.curator.stale_after_days, - archive_after_days=evo.curator.archive_after_days, - ), - ) + return _build_curator_config_response(config) except Exception as e: logger.error("Failed to read curator config: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to read curator config: {str(e)}") @@ -636,6 +641,7 @@ async def update_curator_config(body: CuratorConfigRequest) -> CuratorConfigResp fields are rejected by the Pydantic model_validate call. """ try: + from deerflow.config.app_config import get_app_config, reset_app_config from deerflow.config.skill_evolution_config import SkillEvolutionConfig from deerflow.config.skill_evolution_runtime import save_overrides @@ -644,13 +650,12 @@ async def update_curator_config(body: CuratorConfigRequest) -> CuratorConfigResp SkillEvolutionConfig.model_validate(payload) save_overrides(payload) - # Force the next get_app_config() call to pick up the new overrides. - # mtime check inside get_app_config() will trigger reload automatically, - # but resetting here makes the response below reflect the new state. - from deerflow.config.app_config import reset_app_config + # Reset cache so the next get_app_config() picks up the new overrides + # (the mtime check would catch it too, but resetting makes the response + # below deterministically reflect the new state). reset_app_config() - - return await get_curator_config() + fresh_config = get_app_config() + return _build_curator_config_response(fresh_config) except HTTPException: raise except Exception as e: diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py b/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py index 74477871..1d6859c7 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/tools/skill_manage_tool.py @@ -78,6 +78,10 @@ async def _skill_manage_impl( Args: action: One of create, patch, edit, delete, write_file, remove_file. + For listing or viewing skills, prefer the dedicated + ``skill_list`` / ``skill_view`` tools — but ``action='list'`` + and ``action='view'`` are accepted here as convenience + aliases that delegate to those tools. name: Skill name in hyphen-case. content: New file content for create, edit, or write_file. path: Supporting file path for write_file or remove_file. @@ -85,6 +89,17 @@ async def _skill_manage_impl( replace: Replacement text for patch. expected_count: Optional expected number of replacements for patch. """ + # Read-only aliases — delegate to the dedicated tools so a model that + # over-uses skill_manage doesn't trip on Unsupported action errors. + if action == "list": + from deerflow.tools.builtins.skill_tools import skill_list_tool + + return await _to_thread(skill_list_tool.invoke, {}) + if action == "view": + from deerflow.tools.builtins.skill_tools import skill_view_tool + + return await _to_thread(skill_view_tool.invoke, {"name": name}) + name = SkillStorage.validate_skill_name(name) lock = _get_lock(name) thread_id = _get_thread_id(runtime)