Omnio: honor /skill-name slash commands on the OpenAI chat path - #17
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
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-assignment |
1 |
unresolved-import |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
tests/agent/test_slugify_skill_name.py:5: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
✅ Fixed issues (2):
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
run_agent.py:2984: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
Unchanged: 5933 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
ppazosp
pushed a commit
that referenced
this pull request
Jul 30, 2026
…attr pattern, skill pitfall #17)
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.
Honor
/skill-nameslash commands onPOST /v1/chat/completions(the only path Omnia's chat calls), and surface the brand's invocable commands — including the built-in/learn— onGET /v1/skillsso the client can list them.This re-lands the patch that PR #16 introduced: #16 was stacked on
ppp/sync-upstream-2026-06and its base was never retargeted, so merging it landed the patch in that (now-deleted) sync branch instead ofmain. The upstream sync (#15) is inmain; this brings the slash patch on top of it. Same 5 commits, cherry-picked cleanly onto post-syncmain.What's in it
_maybe_expand_slash_commandruns in_handle_chat_completionsafter the session is resolved: a leading/commandresolves to a bundle then a skill and is rewritten to the canonical invocation message (unmodified, so the sync's memory-extraction scaffolding stays intact);/learnexpands viabuild_learn_prompt; unknown/foopasses straight through to the LLM as plain text. A resolved-but-build-failed command is logged rather than silently dropped.GET /v1/skillscarries the command — each skill gets acommandfield (validated against the liveget_skill_commands()registry viaslugify_skill_name), and the listing appends the/learnbuilt-in (category: "command") so it's endpoint-driven, not hardcoded client-side.Tests
tests/gateway/test_api_server_slash_commands.py,tests/agent/test_slugify_skill_name.py, extendedtests/gateway/test_api_server.py— 201 passing, ruff clean on the post-sync base.Pairs with Omnia PR NousResearch#3315 (merged), which drives the
/palette UI.🤖 Generated with Claude Code
https://claude.ai/code/session_0184TxzJd1SYeBedsTLEfEwM