ResolveSkillCommand checks if the input matches a skill slash command (e.g. /skill-name args). If matched, it reads the skill content and returns the resolved prompt. Otherwise returns "". Fork-mode skills are NOT resolved here; chat dispatches them via SkillCommandFork + RunSkillFork to keep the p
(ctx context.Context, input string)
| 273 | // Fork-mode skills are NOT resolved here; chat dispatches them via |
| 274 | // SkillCommandFork + RunSkillFork to keep the parent transcript clean. |
| 275 | func (a *App) ResolveSkillCommand(ctx context.Context, input string) (string, error) { |
| 276 | if !strings.HasPrefix(input, "/") { |
| 277 | return "", nil |
| 278 | } |
| 279 | |
| 280 | st := a.runtime.CurrentAgentSkillsToolset() |
| 281 | if st == nil { |
| 282 | return "", nil |
| 283 | } |
| 284 | |
| 285 | cmd, arg, _ := strings.Cut(input[1:], " ") |
| 286 | arg = strings.TrimSpace(arg) |
| 287 | |
| 288 | for _, skill := range st.Skills() { |
| 289 | if skill.Name != cmd { |
| 290 | continue |
| 291 | } |
| 292 | |
| 293 | if skill.IsFork() { |
| 294 | // Fall through to ResolveCommand for non-chat callers; the |
| 295 | // chat layer already routed fork-mode skills via |
| 296 | // SkillCommandFork before reaching this point. |
| 297 | return "", nil |
| 298 | } |
| 299 | |
| 300 | content, err := st.ReadSkillContent(ctx, skill.Name) |
| 301 | if err != nil { |
| 302 | return "", fmt.Errorf("reading skill %q: %w", skill.Name, err) |
| 303 | } |
| 304 | |
| 305 | if arg != "" { |
| 306 | return fmt.Sprintf("Use the following skill.\n\nUser's request: %s\n\n<skill name=%q>\n%s\n</skill>", arg, skill.Name, content), nil |
| 307 | } |
| 308 | return fmt.Sprintf("Use the following skill.\n\n<skill name=%q>\n%s\n</skill>", skill.Name, content), nil |
| 309 | } |
| 310 | |
| 311 | return "", nil |
| 312 | } |
| 313 | |
| 314 | // SkillCommandFork returns (skillName, task, true) when input is a slash |
| 315 | // command for a `context: fork` skill, otherwise (_, _, false). Chat layers |