Skip to content

feat(tui): Add inline $skill invocations with SKILL pill + pasteText support - #29217

Open
jjdubski wants to merge 20 commits into
anomalyco:devfrom
jjdubski:dev
Open

feat(tui): Add inline $skill invocations with SKILL pill + pasteText support#29217
jjdubski wants to merge 20 commits into
anomalyco:devfrom
jjdubski:dev

Conversation

@jjdubski

@jjdubskijjdubski commented May 25, 2026

Copy link
Copy Markdown

Issue for this PR

Closes#15617
Closes#10525
Closes#7846
Closes#20982
Closes#24587

Type of change

  • New feature

What does this PR do?

  • This adds inline $skill support to the prompt composer.
  • Typing $ surfaces available skills in autocomplete, and selecting one inserts the skill into the prompt
  • Also works if the user pastes text including the skills, highlighting on shorter paste & longer paste (if paste summary is disabled)
  • Skill gets added to a Set, so skills are added once, and the user prompt maintains original structure for easy copying and editing.

How did you verify your code works?

  • Ran bun test inside the packages/opencode directory
  • Ran bun typecheck
  • User testing (typing in the skills, sending prompts and trying out the paste feature)
image

Screenshots / recordings

Screenshot 2026-05-25 at 8 45 43 AMimage
skill_demo.mp4

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Any feedback would be greatly appreciated!

Typing $ in the prompt now surfaces available skills in the same
autocomplete popup used for @ mentions and / commands. Selecting a
skill inserts $skillname as a highlighted virtual-text extmark.
On submit, $skill references are expanded inline to the full skill
content before the prompt is sent.
- Generalized mentionTriggerIndex() to accept a configurable trigger
character, reused for $ detection alongside @
- Added source.kind discriminator to distinguish skill extmarks from
paste extmarks for correct styling
When using in the prompt, the skill content was expanded
inline at the reference position, overwriting the text and
disrupting the user's prompt structure. The references now remain in
place and skill content is prepended at the beginning of the prompt.
Also removes unused type and function left over from the
original inline-replacement approach, and fixes the autocomplete
dropdown to right-align skill descriptions by padding display names to
a uniform width.
Previously, pasting text containing references into the prompt
inserted plain text with no extmark highlighting, even though the skill
content was correctly prepended on submit via expand(). This made the
pasted references visually inconsistent with type-ahead inserted
skills.
The paste flow now scans the pasted text for references matching
known skills and creates skill-styled extmarks for them, matching the
appearance of autocomplete-inserted skills. Large pastes that get
summarized skip highlighting since the text is hidden behind the
[Pasted ~N lines] placeholder.
@github-actionsgithub-actionsBot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels May 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@magnuslarsen

Copy link
Copy Markdown

Very nice! Can't wait for this to be merged :-)

@mario-loomia

Copy link
Copy Markdown

Awesome! 👏 thanks for picking this up again.

@zcutlip

Copy link
Copy Markdown

Very nice

@jjdubski

Copy link
Copy Markdown
Author

@rekram1-node@thdxr@adamdotdevin Can someone on the team please take a look and provide feedback, any & all is appreciated :)

@i-shm

Copy link
Copy Markdown

very nice work! I really think opencode needs this feature. I built your branch locally and tried it out, and I noticed that when using $ to reference skills, it also inserts $skills name into the prompt. Is that the expected behavior?

Also, could the team please take a look at this PR as soon as possible?

@jjdubski

Copy link
Copy Markdown
Author

very nice work! I really think opencode needs this feature. I built your branch locally and tried it out, and I noticed that when using $ to reference skills, it also inserts $skills name into the prompt. Is that the expected behavior?

Thank you for taking a look and leaving a comment! Yes, that is intended behavior. It matches the current /skills command. It also allows for reuse of the prompt with copy & paste or via the /undo command.

@i-shm

Copy link
Copy Markdown

I noticed that when using Chinese prompts, $skill behaves differently than with English ones, it doesn't seem to insert the skill content properly:

Screencast_20260530_102806.mp4

@jjdubski

Copy link
Copy Markdown
Author

I noticed that when using Chinese prompts, $skill behaves differently than with English ones, it doesn't seem to insert the skill content properly:

Screencast_20260530_102806.mp4

Seems to format the prompt correctly but then hitting enter (sending the prompt) causes it to behave weirdly.

Can you copy and paste the prompt you used into the prompt window and see if this happens again?

Also were you running the build? Or bun dev?

Are you using Mac, Windows or Linux?

Does this happen with just DeepSeek V4 Flash Free or with other models as well?

Please answer the questions and provide the prompt you used before sending to the agent, and I will try to reproduce/fix.

@i-shm

i-shm commented May 30, 2026

Copy link
Copy Markdown

I noticed that when using Chinese prompts, $skill behaves differently than with English ones, it doesn't seem to insert the skill content properly:

Screencast_20260530_102806.mp4

Seems to format the prompt correctly but then hitting enter (sending the prompt) causes it to behave weirdly.

Can you copy and paste the prompt you used into the prompt window and see if this happens again?

Also were you running the build? Or bun dev?

Are you using Mac, Windows or Linux?

Does this happen with just DeepSeek V4 Flash Free or with other models as well?

Please answer the questions and provide the prompt you used before sending to the agent, and I will try to reproduce/fix.

I tried again and can consistently reproduce the issue, but I noticed something new, when the prompt starts with a non-Chinese character, the skill gets inserted correctly. But if it starts with a Chinese character, it breaks. Here's my prompt: 你好 $analyzing-codex-token-usage 和 $ascii-art-diagrams

I tested it with other models and they all have the same problem. I'm using an executable built from your branch at commit d33cf6d on Linux.

Screencast_20260530_122609.mp4

When a CJK character (display-width 2, string-length 1) preceded a
in the prompt, the extmark submission loop would slice
inputText at the wrong character offset — its positions were stored as
display-width values from Bun.stringWidth — producing garbled references
like -testing instead of -testing.
Skills now use type: skill (matching the @ agent/file pattern) instead
of type: text with kind: skill. The extmark loop only processes
type: text parts, so skills bypass the display-offset slice entirely.
The SkillPartInput type was added to the SDK's parts union and the
local SkillPart/TuiSkillPart types were replaced with the canonical
SDK type. Backward compatibility with old prompt-history entries that
use the type: text + kind: skill format is preserved.
@jjdubski

Copy link
Copy Markdown
Author

@i-shm Thank you for your testing!

The issue was when a CJK character (width 2, length 1) preceded a $skillname in the prompt, submission corrupted the reference — e.g. $w$webapp-testing instead of $webapp-testing. The extmark system stored positions as display-width offsets (Bun.stringWidth) but used them to slice() inputText with character offsets, so every preceding CJK char shifted the slice by one extra position.

Fix: Refactored skills to use type: "skill" (matching the @ agent/file pattern) instead of type: "text" with kind: "skill". The extmark submission loop only processes type: "text" parts, so skills bypass the display-width-offset slice() entirely. The SkillPart & SkillPartInput type was added to the SDK to fix this.

  • Passed bun typecheck
  • Passed all tests in 'bun test`
  • Able to build locally and run properly
  • All user testing from before is working + trying CJK characters seems to work properly now as well

TLDR: It should work now & the code quality is better! Try it out and let me know if you have any issues.

…end SkillPartInput as structured parts, guard shell var autocomplete
- Restore lost pasteStyleId on text-part extmark restore (index.tsx)
- Remove duplicate has() in skill.ts; reuse references() in expand()
- Drop redundant has() guard before expand() on submit (double iteration)
- Suppress $ autocomplete for all-caps env vars like /Users/jake/.bun/bin:/Users/jake/.opencode/bin:/opt/homebrew/opt/ruby/bin:/Users/jake/.nvm/versions/node/v24.14.1/bin:/opt/homebrew/bin/python3:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/Users/jake/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/jake/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/Library/Frameworks/Python.framework/Versions/3.11/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/usr/local/share/dotnet:~/.dotnet/tools:/usr/local/go/bin:/Users/jake/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand:/Users/jake/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli:/usr/local/share/dotnet:/opt/homebrew/lib/ruby/gems/4.0.0/bin (autocomplete.tsx)
- Add SkillPart/SkillPartInput Effect Schemas to server-side Part union (message-v2.ts)
- Wire SkillPartInput into PromptInput schema and resolvePart handler (prompt.ts)
- Remove type: skill filter in TUI submit path so skill parts send as structured input
…essages
Replace the client-side expand() call with server-side SkillPart resolution,
so skill expansion happens once per structured part (fixing a duplication
bug when the prompt was restored via /undo). The stays in the text
for editing; the server returns both the SkillPart and a synthetic text
part with the expanded content.
A submit-time scan ensures every in the input gets a structured
SkillPartInput, even when typed manually or pasted.
/undo now preserves skill parts when reconstructing the prompt, and
restoreExtmarksFromParts has a defensive fallback that locates
in the text when source is missing.
Sent messages display SKILL <name> chips (matching the file-chip pattern)
so the user can see which skills were invoked.
@jjdubskijjdubski changed the title feat(TUI): Add inline $skill invocations with prepend + pasteText supportfeat(tui): Add inline $skill invocations with SKILL pill + pasteText supportMay 30, 2026
@sharunkumar

Copy link
Copy Markdown

I would prefer if its /skill-name actually rather than $ 🤔

@jjdubski

Copy link
Copy Markdown
Author

I would prefer if its /skill-name actually rather than $ 🤔

See @rekram1-node comment on #25439, they are trying to move away from / command based invocation

@Thevetat

Copy link
Copy Markdown

Hope this gets through. It would make interoperability with the codex cli more complete as well. I have been moving codex workflows over to opencode, and have been having to use command shims to invoke skills, but can not use them inline still so had to rewrite a fair bit.

@jjdubski

Copy link
Copy Markdown
Author

Hope this gets through. It would make interoperability with the codex cli more complete as well. I have been moving codex workflows over to opencode, and have been having to use command shims to invoke skills, but can not use them inline still so had to rewrite a fair bit.

Thank you for the feedback! I hope it gets merged as well, every comment and reaction will help to bring more attention, hopefully the team will notice & approve of the update 😄

@StevanusPangau

Copy link
Copy Markdown

is there any update for this PR?

@alexx855alexx855 mentioned this pull request Jun 19, 2026
6 tasks
@vuon9

vuon9 commented Jun 22, 2026

Copy link
Copy Markdown

This is nice! May I ask some questions?

  • can the loaded skills be hidden as default? to keep the ui in minimal style like the current.
  • can the loaded skills be shown on the sidebar? or anywhere else as long as it can be hidden by default.
  • can it has ability to not re-inject the loaded skill? for working on long sessions over many days, people would forget.

Sorry if I misread any of the feature.

@jjdubski

Copy link
Copy Markdown
Author

@vuon9 Thank you for taking a look

  • can the loaded skills be hidden as default? to keep the ui in minimal style like the current.

Currently it will show underneath the prompt to show it was loaded. Matches the current design of mentioning a file/directory

  • can the loaded skills be shown on the sidebar? or anywhere else as long as it can be hidden by default.

They are not added currently because it can be removed via compaction. In order to use the skills properly they need to be injected into the prompt not into a session.

  • can it has ability to not re-inject the loaded skill? for working on long sessions over many days, people would forget.

Same problem with compaction. The full context of the skills is silently compressed/erased. It could technically be done but then skills would have to be reinjected after the prompt, not sure if that is a good idea because context will be used up quickly and could trigger a loop of constant compaction.

@generik0

Copy link
Copy Markdown

plz can we get this merged?? :-)
Awesome work!

@Bedirhandd

Copy link
Copy Markdown

As a community we need this ASAP, good work mate

@Jesse-duanji

Copy link
Copy Markdown

When can this be merged. Can't wait for this new feature.

export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached media from tool result:"
export { isMedia }


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this was intentional or not.

Suggested change

return `${text.slice(0, maxChars)}\n[Tool output truncated for compaction: omitted ${omitted} chars]`
}


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@kaishin

Copy link
Copy Markdown

This is the only reason I have forked the repository. Let's get this merged please.

@dhakan

Copy link
Copy Markdown

It's really quite annoying to keep opening /skills in order to search properly... Please get on this

@igorwfaoro

Copy link
Copy Markdown

Small note for the author: the branch is getting stale and needs a rebase with the current dev.

I tested this PR locally against today's dev and it no longer applies cleanly:

  • The branch has diverged from dev quite a bit, which is why GitHub shows the mergeable state as dirty.
  • The three commits (d58aaceb0, 58d5f53d4, 8687a7a8f) don't cherry-pick cleanly onto the current dev. There are conflicts in packages/opencode/src/session/prompt.ts, packages/opencode/src/session/message-v2.ts, packages/tui/src/component/prompt/index.tsx and packages/opencode/test/session/prompt.test.ts.
  • The prompt.test.ts on this branch still imports modules that were refactored or moved upstream (for example @opencode-ai/core/effect/layer-node, SessionProjector, and Shell from @opencode-ai/core/shell), so it doesn't run against the current dev.

The inline $skill feature itself looks great and the community clearly wants it, it just needs to be brought up to date before it can merge. Happy to re-test once it's rebased.

@w0rsti

Copy link
Copy Markdown

One thing worth noting in relation to the points already raised: the GUI implementation does work to some extent, but it is not actually doing a native skill invocation. It replaces the / invokation with the skill content into the prompt, which often causes the model to recognize and load the same skill again through its built-in skill discovery flow. In practice, this can result in the skill context being processed twice and increases token usage.

More importantly: Opencode's v2 beta already supports proper / invocation semantics. From my testing, it invokes the skill directly instead of treating the slash command as text that gets replaced by the skill contents first, so it avoids the double-loading behavior described above.

@jjdubski

Copy link
Copy Markdown
Author

@igorwfaoro@w0rsti thanks for looking into it and providing your input, I have been maintaining the fork via a different branch that has some other features that fit my workflow.

I'll look into rebasing this branch and fixing the conflicts to get it up to date this weekend.

The way /skills works currently is pasting the skill content into the prompt when the user sends the prompt, which is what this mimics but shows the pill instead of actually showing the skill content to allow for easier readability. I agree it should use a proper invocation to avoid the double loading but in my testing it doesn't seem to do so, perhaps it depends on the model and if it recognizes if the skill is loaded in. I'll look into it more and see if I can improve it.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

17 participants

@jjdubski@magnuslarsen@mario-loomia@zcutlip@i-shm@sharunkumar@Thevetat@StevanusPangau@vuon9@generik0@Bedirhandd@Jesse-duanji@kaishin@dhakan@igorwfaoro@w0rsti@jaydenfyi