Commit c172866
authored
🤖 feat: expose agent skills as slash commands (#1673)
Expose Agent Skills as slash commands.
- Adds `agentSkills.list` / `agentSkills.get` oRPC endpoints so the UI
can discover/read skills.
- Surfaces skills in `/` autocomplete and supports `/skill-name
<message>` to apply a skill for that send.
- Injects the selected skill body via `additionalSystemInstructions` and
annotates the message with `muxMetadata` so history shows the original
`/skill-name ...` text.
Validation:
- `bun test src/browser/utils/slashCommands/suggestions.test.ts`
- `bun test src/cli/server.test.ts`
- `make static-check`
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Expose Agent Skills as Slash Commands
## Goal
Make **Agent Skills** discoverable and invokable via the chat **slash
command** UX (type `/` → autocomplete), so a user can explicitly apply a
skill and have it included in the model context for that send.
## Recommended approach: “Skill-as-command” + per-send context injection
**Net LoC estimate (product code only): ~350–550 LoC**
### UX / behavior
- **Autocomplete:** skills appear alongside existing commands in the `/`
suggestions list.
- Display: `/{skillName}` with description.
- Show scope hint (`(project)`, `(user)`, `(built-in)`).
- **Invocation:** user writes:
- `/{skillName} <their message>`
- **On send:**
1. Detect that the first token is a known skill name.
2. Load the skill package from backend.
3. Send the remainder of the user’s message normally, but inject the
skill body into **system context** for this request.
---
## Implementation plan
### 1) Backend: expose Agent Skills over ORPC
1. **Add ORPC schemas** in `src/common/orpc/schemas/api.ts`:
- `agentSkills.list` → returns `AgentSkillDescriptor[]`
- `agentSkills.get` → returns `AgentSkillPackage` for a `skillName`
- Reuse `SkillNameSchema` / `AgentSkillDescriptorSchema` /
`AgentSkillPackageSchema` from `src/common/orpc/schemas/agentSkill.ts`.
2. **Add ORPC handlers** in `src/node/orpc/router.ts`:
- `agentSkills.list`: call `discoverAgentSkills(runtime,
discoveryPath)`.
- `agentSkills.get`: call `readAgentSkill(...)` and return
`result.package`.
3. **Defensive limits:**
- Enforce a **max injected skill body size** (chars) to avoid exploding
system prompts (truncate with a clear sentinel).
- Treat read failures as non-fatal: return a typed error to the UI.
### 2) Frontend: fetch skill descriptors for suggestions
1. **Load skill descriptors** in
`src/browser/components/ChatInput/index.tsx` similarly to provider list:
- `const [agentSkillDescriptors, setAgentSkillDescriptors] =
useState<AgentSkillDescriptor[]>([])`
- `useEffect(() => api.agentSkills.list(...))`
2. Cache policy:
- Refresh on `workspaceId` / `projectPath` change.
- (Optional later) hot-reload via fs watcher / event stream.
### 3) Slash suggestions: include skills
1. Extend `SlashSuggestionContext`
(`src/browser/utils/slashCommands/types.ts`) to accept skill
descriptors:
- `agentSkills?: AgentSkillDescriptor[]`.
2. Update `getSlashCommandSuggestions()`
(`src/browser/utils/slashCommands/suggestions.ts`) to merge:
- existing static command suggestions
- **dynamic skill suggestions** (alphabetical; filtered by partial
token)
3. Update `CommandSuggestions` UI
(`src/browser/components/CommandSuggestions.tsx`) to visually
distinguish skills:
- Either extend `SlashSuggestion` with `kind` + `rightLabel`, or bake
scope into `description`.
### 4) Sending pipeline: parse + inject skill context
1. In `ChatInput/index.tsx` send handler:
- Before calling `parseCommand()`, detect `/{skillName}` as the first
token (using `agentSkillDescriptors`).
2. If it’s a skill invocation:
- Call `api.agentSkills.get({ skillName, ...discoveryContext })`.
- Compute:
- `userMessageTextWithoutCommand`
- `additionalSystemInstructions` that wraps the skill content, e.g.:
- header: `Agent Skill applied: {name} ({scope})`
- body: the skill markdown `package.body`
- Send via `api.workspace.sendMessage({ message, options: {
...sendMessageOptions, additionalSystemInstructions } })`.
3. Optional but recommended: annotate UI via `muxMetadata` so the
transcript shows which skill was applied.
### 5) Edge cases to handle
- User sends only `/{skillName}` (no remainder message): intercept and
avoid sending an empty message.
- Unknown `/{token}`: fall back to existing behavior (normal slash
command parse; else send as plain text).
- Multiple skills in one line: MVP supports only the first token.
### 6) Tests
- Unit tests for:
- suggestion list merging + filtering
- splitting `/{skillName} rest of message`
- Integration tests for ORPC endpoints:
- `agentSkills.list` returns descriptors
- `agentSkills.get` returns package + respects project/global precedence
---
<details>
<summary>Notes / rationale</summary>
- Mux already lists skill descriptors in the system prompt
(`buildAgentSkillsContext()`), enabling model-initiated tool calls. This
plan adds explicit user invocation + UI discoverability without changing
the core tool-call mechanism.
- Per-send injection avoids introducing persistent “pinned skill” state.
</details>
</details>
---
_Generated with [`mux`](https://github.com/coder/mux) • Model:
`openai:gpt-5.2` • Thinking: `xhigh` • Cost: $1.29_
---------
Signed-off-by: Thomas Kosiewski <tk@coder.com>1 parent b895f43 commit c172866
27 files changed
Lines changed: 1231 additions & 112 deletions
File tree
- docs/agents
- scripts
- src
- browser
- components
- ChatInput
- utils
- messages
- slashCommands
- cli
- common
- orpc
- schemas
- types
- node
- builtinSkills
- orpc
- services
- agentSkills
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | | - | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
80 | 81 | | |
81 | 82 | | |
82 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
83 | 91 | | |
84 | 92 | | |
85 | 93 | | |
| |||
101 | 109 | | |
102 | 110 | | |
103 | 111 | | |
104 | | - | |
| 112 | + | |
| 113 | + | |
105 | 114 | | |
106 | 115 | | |
107 | 116 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
11 | 21 | | |
12 | 22 | | |
13 | 23 | | |
| |||
0 commit comments