Omnio: honor /skill-name slash commands on the OpenAI chat path - #16
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The OpenAI-compatible
/v1/chat/completionsendpoint was the only Hermes surface that didn't honor slash commands — the CLI, TUI, and messaging gateway all expand a leading/skill-nameinto its skill-invocation payload, but a chat-completions client (Omnia's Omnio chat) got the raw/fooforwarded to the model as text. This teaches the chat path the same dispatch (skills, bundles, and the/learnbuilt-in), and exposes the canonical command on/v1/skillsso a client can build a slash menu whose entries always resolve.Stacks on #15 (the upstream sync) — based on
ppp/sync-upstream-2026-06so the diff here is only this feature. Retarget tomainonce #15 merges.What's in it
_maybe_expand_skill_command, wired into_handle_chat_completionsonce the session id is resolved: a recognized/command(bundles take precedence, mirroringgateway/run.py) has the user turn replaced with the unmodifiedbuild_skill_invocation_messageoutput 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/skillsnow returns acommandslug per skill, derived by a sharedslugify_skill_nameand validated against the live command registry so a non-nullcommandis 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).SKILL.mdremoved/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-guidedbuild_learn_prompt, driving the agent to author a skill viaskill_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
str, so a/skilltyped alongside an attachment is forwarded as-is. Acceptable — the palette's skills are text-instruction driven./v1/responsespath is intentionally not wired (Omnio chat uses/v1/chat/completions)._maybe_expand_skill_command(match / bundle-precedence / unknown-passthrough / multimodal-passthrough / resolved-but-failed logging),slugify_skill_name, and the/v1/skillscommand validation.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bit68U5GXoHUds92TE5XVp