feat(skills): stacked slash-skill invocations — /skill-a /skill-b do XYZ (#57987)

Inspired by Claude Code v2.1.199 (July 2, 2026): stacked slash-skill
invocations load all leading skills (up to 5), not just the first.

- agent/skill_commands.py: split_stacked_skill_commands() consumes leading
  /skill tokens (stops at the first non-skill token so slash-path arguments
  are never swallowed); build_stacked_skill_invocation_message() composes
  the multi-skill turn reusing the existing bundle scaffolding markers so
  extract_user_instruction_from_skill_message() keeps memory providers
  storing the user's instruction, not N skill bodies.
- cli.py + gateway/run.py: dispatch the stacked path on both surfaces.
- 11 new tests + docs section in skills.md.
This commit is contained in:
Teknium 2026-07-05 02:20:01 -07:00 committed by GitHub
parent 30479961b8
commit 9767e19b60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 363 additions and 7 deletions

View file

@ -9846,12 +9846,39 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
f"Enable it with: `hermes skills config`"
)
user_instruction = event.get_command_args().strip()
msg = build_skill_invocation_message(
cmd_key, user_instruction, task_id=_quick_key
)
if msg:
event.text = msg
# Fall through to normal message processing with skill content
# Stacked slash-skill invocations: `/skill-a /skill-b do
# XYZ` loads every leading skill (up to 5), not just the
# first. Inspired by Claude Code v2.1.199. Mirrors CLI.
try:
from agent.skill_commands import (
build_stacked_skill_invocation_message as _build_stacked,
split_stacked_skill_commands,
)
extra_keys, stacked_instruction = (
split_stacked_skill_commands(user_instruction)
)
except Exception:
_build_stacked = None
extra_keys, stacked_instruction = [], user_instruction
if extra_keys and _build_stacked is not None:
stacked_result = _build_stacked(
[cmd_key, *extra_keys],
stacked_instruction,
task_id=_quick_key,
)
if stacked_result:
msg, _loaded, _missing = stacked_result
event.text = msg
# Fall through to normal message processing
else:
return f"Failed to load stacked skills for /{command}."
else:
msg = build_skill_invocation_message(
cmd_key, user_instruction, task_id=_quick_key
)
if msg:
event.text = msg
# Fall through to normal message processing with skill content
else:
# Not an active skill — check if it's a known-but-disabled or
# uninstalled skill and give actionable guidance.