hermes-bsd/tools
Teknium 8c8fc6c1ec
fix(skills): let skill_manage patch/edit/delete skills in external_dirs in place (#17512)
Closes #4759, closes #4381.

Mutating actions (patch, edit, write_file, remove_file, delete) used to
refuse skills that lived under `skills.external_dirs` with 'Skill X is in
an external directory and cannot be modified. Copy it to your local skills
directory first.'  Faced with that error, the agent would fall back to
action='create', which always writes under ~/.hermes/skills/ — producing
a silent duplicate of the external skill in the local store.

Fix: drop the read-only gate.  `skills.external_dirs` is configured by the
user; if they pointed it at a directory, they already said 'these are my
skills, treat them the same.'  Filesystem permissions handle the genuine
read-only case (write fails, agent sees the error).

- New _containing_skills_root() resolves whichever dir actually contains
  the skill; _delete_skill uses it to bound empty-category cleanup so an
  external root is never rmdir'd.
- _create_skill behavior is unchanged: new skills still land in local
  SKILLS_DIR only.  Fewer moving parts.
- Seven new TestExternalSkillMutations tests covering patch/edit/write_file/
  remove_file/delete/create against a mocked two-root layout + a category
  rmdir-safety check.
2026-04-29 08:16:52 -07:00
..
browser_providers
environments fix: address self-review findings for Vercel Sandbox salvage 2026-04-29 07:22:33 -07:00
neutts_samples
__init__.py
ansi_strip.py
approval.py feat: add Vercel Sandbox backend 2026-04-29 07:22:33 -07:00
binary_extensions.py
browser_camofox.py refactor(config): add cfg_get() helper; migrate 20 nested-get call sites (#17304) 2026-04-28 23:17:39 -07:00
browser_camofox_state.py
browser_cdp_tool.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
browser_dialog_tool.py
browser_supervisor.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
browser_tool.py refactor(config): add cfg_get() helper; migrate 20 nested-get call sites (#17304) 2026-04-28 23:17:39 -07:00
budget_config.py
checkpoint_manager.py feat(checkpoints): auto-prune orphan and stale shadow repos at startup (#16303) 2026-04-26 19:05:52 -07:00
clarify_tool.py
code_execution_tool.py feat: add Vercel Sandbox backend 2026-04-29 07:22:33 -07:00
credential_files.py refactor(config): add cfg_get() helper; migrate 20 nested-get call sites (#17304) 2026-04-28 23:17:39 -07:00
cronjob_tools.py fix(cron): accept list-form deliver values so deliver=['telegram'] works (#17456) 2026-04-29 06:35:34 -07:00
debug_helpers.py
delegate_tool.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
discord_tool.py fix(discord_tool): coerce limit parameter to int before min() call 2026-04-26 20:48:38 -07:00
env_passthrough.py refactor(config): add cfg_get() helper; migrate 20 nested-get call sites (#17304) 2026-04-28 23:17:39 -07:00
feishu_doc_tool.py
feishu_drive_tool.py
file_operations.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00
file_state.py
file_tools.py feat: add Vercel Sandbox backend 2026-04-29 07:22:33 -07:00
fuzzy_match.py
homeassistant_tool.py
image_generation_tool.py
interrupt.py
managed_tool_gateway.py
mcp_oauth.py fix(mcp-oauth): preserve server_url path for protected-resource validation (#16031) 2026-04-26 05:43:54 -07:00
mcp_oauth_manager.py fix(mcp-oauth): preserve server_url path for protected-resource validation (#16031) 2026-04-26 05:43:54 -07:00
mcp_tool.py refactor(schema): consolidate nullable-union stripping in schema_sanitizer 2026-04-28 04:58:03 -07:00
memory_tool.py refactor: consolidate symlink-safe atomic replace into shared helper 2026-04-28 04:58:22 -07:00
mixture_of_agents_tool.py
neutts_synth.py
openrouter_client.py
osv_check.py
patch_parser.py
path_security.py
process_registry.py fix(process): reconcile session.exited against real child exit in poll/wait (#17430) 2026-04-29 04:59:21 -07:00
registry.py perf(tools): memoize get_tool_definitions + TTL-cache check_fn results (#17098) 2026-04-28 18:20:17 -07:00
rl_training_tool.py
schema_sanitizer.py refactor(schema): consolidate nullable-union stripping in schema_sanitizer 2026-04-28 04:58:03 -07:00
send_message_tool.py feat(yuanbao): wire native text + media delivery into send_message 2026-04-29 04:56:18 -07:00
session_search_tool.py fix(session-search): exclude current lineage root deterministically in recent mode 2026-04-26 19:03:17 -07:00
skill_manager_tool.py fix(skills): let skill_manage patch/edit/delete skills in external_dirs in place (#17512) 2026-04-29 08:16:52 -07:00
skill_usage.py fix(curator): defense-in-depth gates against bundled/hub skills 2026-04-28 22:33:33 -07:00
skills_guard.py
skills_hub.py feat(skills): install skills from a direct HTTP(S) URL (#16323) 2026-04-26 20:57:10 -07:00
skills_sync.py refactor: consolidate symlink-safe atomic replace into shared helper 2026-04-28 04:58:22 -07:00
skills_tool.py feat: add Vercel Sandbox backend 2026-04-29 07:22:33 -07:00
terminal_tool.py feat: add Vercel Sandbox backend 2026-04-29 07:22:33 -07:00
tirith_security.py
todo_tool.py
tool_backend_helpers.py fix(cli): coerce use_gateway config flags in tool routing 2026-04-26 19:02:55 -07:00
tool_output_limits.py
tool_result_storage.py
transcription_tools.py fix(stt): resolve API keys from ~/.hermes/.env via get_env_value (#17140) 2026-04-29 06:25:20 -07:00
tts_tool.py fix(tts): tolerate missing hermes_cli.config in tts_tool import 2026-04-29 06:25:20 -07:00
url_safety.py fix(security): treat quoted false as false in browser SSRF guards 2026-04-26 18:27:13 -07:00
vision_tools.py refactor(config): add cfg_get() helper; migrate 20 nested-get call sites (#17304) 2026-04-28 23:17:39 -07:00
voice_mode.py
web_tools.py perf(tools): memoize get_tool_definitions + TTL-cache check_fn results (#17098) 2026-04-28 18:20:17 -07:00
website_policy.py
xai_http.py
yuanbao_tools.py chore: remove unused imports and dead locals (ruff F401, F841) (#17010) 2026-04-28 06:46:45 -07:00