diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 8937e52048e7..6972f1de41a7 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -146,7 +146,10 @@ import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel"; import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner"; import { ComposerControl, ComposerControlIcon, ComposerSelectControl } from "./ComposerControl"; import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight"; -import { searchSlashCommandItems } from "./composerSlashCommandSearch"; +import { + searchSlashCommandItems, + slashCommandItemsForPromptPosition, +} from "./composerSlashCommandSearch"; import { getComposerPromptInjectionState, getComposerProviderState, @@ -1360,11 +1363,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) skill.description ?? (skill.scope ? `${skill.scope} skill` : ""), })); - const slashCommandItems = [ - ...builtInSlashCommandItems, - ...providerSlashCommandItems, - ...skillItems, - ]; + const slashCommandItems = slashCommandItemsForPromptPosition( + [...builtInSlashCommandItems, ...providerSlashCommandItems, ...skillItems], + composerTrigger.rangeStart === 0, + ); return searchSlashCommandItems(slashCommandItems, query); } if (composerTrigger.kind === "skill") { diff --git a/apps/web/src/components/chat/composerSlashCommandSearch.test.ts b/apps/web/src/components/chat/composerSlashCommandSearch.test.ts index be749c0aae47..751aced7b835 100644 --- a/apps/web/src/components/chat/composerSlashCommandSearch.test.ts +++ b/apps/web/src/components/chat/composerSlashCommandSearch.test.ts @@ -2,7 +2,10 @@ import { describe, expect, it } from "vite-plus/test"; import { ProviderDriverKind } from "@t3tools/contracts"; import type { ComposerCommandItem } from "./ComposerCommandMenu"; -import { searchSlashCommandItems } from "./composerSlashCommandSearch"; +import { + searchSlashCommandItems, + slashCommandItemsForPromptPosition, +} from "./composerSlashCommandSearch"; describe("searchSlashCommandItems", () => { const claudeDriver = ProviderDriverKind.make("claudeAgent"); @@ -173,4 +176,38 @@ describe("searchSlashCommandItems", () => { "skill:claudeAgent:unslop", ]); }); + + it("hides skills from slash completion after the first message line", () => { + const items = [ + { + id: "slash:model", + type: "slash-command", + command: "model", + label: "/model", + description: "Switch model", + }, + { + id: "skill:claudeAgent:unslop", + type: "skill", + provider: claudeDriver, + skill: { + name: "unslop", + path: "/skills/unslop/SKILL.md", + enabled: true, + }, + label: "/skill:unslop", + description: "Cut AI tells from writing", + }, + ] satisfies Array< + Extract + >; + + expect(slashCommandItemsForPromptPosition(items, false).map((item) => item.id)).toEqual([ + "slash:model", + ]); + expect(slashCommandItemsForPromptPosition(items, true).map((item) => item.id)).toEqual([ + "slash:model", + "skill:claudeAgent:unslop", + ]); + }); }); diff --git a/apps/web/src/components/chat/composerSlashCommandSearch.ts b/apps/web/src/components/chat/composerSlashCommandSearch.ts index 3e60cbf58b33..1578e0ec6f86 100644 --- a/apps/web/src/components/chat/composerSlashCommandSearch.ts +++ b/apps/web/src/components/chat/composerSlashCommandSearch.ts @@ -12,6 +12,16 @@ type SlashSearchItem = Extract< { type: "slash-command" | "provider-slash-command" | "skill" } >; +export function slashCommandItemsForPromptPosition( + items: ReadonlyArray, + isAtPromptStart: boolean, +): SlashSearchItem[] { + if (isAtPromptStart) { + return [...items]; + } + return items.filter((item) => item.type !== "skill"); +} + function scoreSlashCommandItem(item: SlashSearchItem, query: string): number | null { if (item.type === "skill") { if (query === "skill") {