Skip to content

Omnio: honor /skill-name slash commands on the OpenAI chat path - #16

Merged
ppazosp merged 5 commits into
ppp/sync-upstream-2026-06from
ppp/omnio-slash-commands
Jun 25, 2026
Merged

Omnio: honor /skill-name slash commands on the OpenAI chat path#16
ppazosp merged 5 commits into
ppp/sync-upstream-2026-06from
ppp/omnio-slash-commands

Conversation

@ppazosp

@ppazosp ppazosp commented Jun 24, 2026

Copy link
Copy Markdown

The OpenAI-compatible /v1/chat/completions endpoint was the only Hermes surface that didn't honor slash commands — the CLI, TUI, and messaging gateway all expand a leading /skill-name into its skill-invocation payload, but a chat-completions client (Omnia's Omnio chat) got the raw /foo forwarded to the model as text. This teaches the chat path the same dispatch (skills, bundles, and the /learn built-in), and exposes the canonical command on /v1/skills so a client can build a slash menu whose entries always resolve.

Stacks on #15 (the upstream sync) — based on ppp/sync-upstream-2026-06 so the diff here is only this feature. Retarget to main once #15 merges.

What's in it

  • _maybe_expand_skill_command, wired into _handle_chat_completions once the session id is resolved: a recognized /command (bundles take precedence, mirroring gateway/run.py) has the user turn replaced with the unmodified build_skill_invocation_message output before the agent runs, so the memory layer's invocation markers stay intact. Unlike the messaging gateway, the chat API carries general prose, so a / that matches no skill (a path like /Users/x, a question about /etc) passes through untouched — only confirmed matches are intercepted.
  • GET /v1/skills now returns a command slug per skill, derived by a shared slugify_skill_name and validated against the live command registry so a non-null command is guaranteed to resolve on the chat path. This rejects slugs that don't round-trip (display-truncated names) and per-platform-disabled/incompatible skills (absent from the registry → command: null).
  • A command that resolves to a real skill/bundle but then builds no payload (e.g. a SKILL.md removed/unreadable on disk) is logged at error level instead of silently degrading to raw passthrough — so it doesn't look like the skill "did nothing".
  • /learn [what to learn from] is honored too — the one built-in worth surfacing on the chat path (no Omnia-UI equivalent). It rewrites the turn to the standards-guided build_learn_prompt, driving the agent to author a skill via skill_manage (which then shows up under "Your skills" in the palette). Dispatched before skill/bundle resolution; side-effecting built-ins (/new, /yolo, …) are deliberately not handled.

Notes

  • Only plain-text turns expand: a multimodal (text + image) turn arrives as a content list rather than a str, so a /skill typed alongside an attachment is forwarded as-is. Acceptable — the palette's skills are text-instruction driven.
  • The /v1/responses path is intentionally not wired (Omnio chat uses /v1/chat/completions).
  • Tests cover _maybe_expand_skill_command (match / bundle-precedence / unknown-passthrough / multimodal-passthrough / resolved-but-failed logging), slugify_skill_name, and the /v1/skills command validation.
  • Consumed by the Omnia palette PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp

ppazosp and others added 5 commits June 24, 2026 21:47
The OpenAI chat path is the only Hermes surface that did not honor slash
commands — CLI, TUI and the messaging gateway all expand a leading
/skill-name into its skill-invocation payload, but a chat-completions
client (Omnia's Omnio chat) got the raw "/foo" forwarded to the model as
plain text.

Add _maybe_expand_skill_command and call it in _handle_chat_completions
once the session id is resolved: a recognized /command is replaced with
the unmodified build_skill_invocation_message / build_bundle_invocation_message
output (bundles take precedence, mirroring gateway/run.py dispatch order).
Unlike the messaging gateway, the chat API carries general prose, so a
message that merely starts with "/" and matches no skill (a path, a
question about /etc) passes through untouched rather than being rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp
A "/" skill palette needs the exact command string to send for each skill;
deriving the slug client-side would risk drifting from Hermes' own
normalization and silently producing a "/foo" that never resolves.

Extract slugify_skill_name as the single source of truth (lower, spaces/
underscores to hyphens, drop invalid chars, collapse + trim hyphens), reuse
it in scan_skill_commands, and add a `command` field to each /v1/skills
entry (null when the name reduces to an empty slug). Clients render and send
"/<command>" and it is guaranteed to resolve.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp
- A "/command" that RESOLVES to a real skill/bundle but then builds no
  payload (e.g. a SKILL.md removed/unreadable) was silently forwarded to the
  model as raw text. Log it at error level (and use logger.exception for the
  unexpected-exception branches) so the failure is debuggable instead of
  looking like the skill "did nothing".
- /v1/skills now validates each derived command against the live command
  registry: a non-null `command` is guaranteed to resolve on the chat path.
  This rejects slugs that don't round-trip (display-truncated names) and
  per-platform-disabled/incompatible skills (absent from the registry), which
  report command=null rather than a command that silently fails to invoke.
- Document that multimodal (text+image) turns don't expand (content is a list,
  not a str).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp
/learn is the one built-in command worth honoring on the OpenAI chat path
(no Omnia-UI equivalent): like the gateway, it rewrites the turn to the
standards-guided build_learn_prompt that drives the agent to author a skill
via skill_manage. Renamed _maybe_expand_skill_command -> _maybe_expand_slash_command
and dispatch /learn before skill/bundle resolution. Side-effecting built-ins
(/new, /yolo, …) are still deliberately not handled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp
…arn")

The palette was hardcoding the /learn command client-side, so it showed even
when the sprite was cold. Return it from /v1/skills instead (category
"command", name "learn") so the whole palette is endpoint-driven — empty until
the gateway responds — and the displayed name is the command itself, not prose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp
@ppazosp
ppazosp merged commit a7dbb31 into ppp/sync-upstream-2026-06 Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant