From c5da7505dadb6bea29ded0862388379173eb771a Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 12 Jun 2026 12:28:06 +0700 Subject: [PATCH] Show Claude skills in inline autocomplete --- .../agent/providers/claude/agent.test.ts | 82 +++++++++++++++++++ .../server/agent/providers/claude/agent.ts | 21 ++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/packages/server/src/server/agent/providers/claude/agent.test.ts b/packages/server/src/server/agent/providers/claude/agent.test.ts index 333899b4f..262999109 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -999,6 +999,88 @@ describe("ClaudeAgentSession context window usage", () => { expect(persistedQueryFactory.mock.calls[0]?.[0].options.persistSession).toBe(true); }); + test("classifies Claude root-only commands separately from inline skills", async () => { + const queryFactory = vi.fn(({ prompt }: { prompt: AsyncIterable }) => { + void prompt; + return { + next: async () => ({ done: true, value: undefined }), + interrupt: async () => undefined, + return: async () => undefined, + close: () => undefined, + setPermissionMode: async () => undefined, + setModel: async () => undefined, + supportedModels: async () => [], + supportedCommands: async () => [ + { + name: "taste", + description: "Use when another skill needs the shared standard. (user)", + argumentHint: "", + }, + { + name: "claude-api", + description: "Build, debug, and optimize Claude API apps with this skill.", + argumentHint: "", + }, + { + name: "usage", + description: "Show the total cost and duration of the current session", + argumentHint: "", + }, + { + name: "clear", + description: "Start a new session with empty context", + argumentHint: "", + }, + ], + rewindFiles: async () => ({ canRewind: true }), + [Symbol.asyncIterator]() { + return this; + }, + }; + }); + const client = new ClaudeAgentClient({ + logger, + queryFactory, + resolveBinary: async () => "/test/claude/bin", + }); + const session = await client.createSession({ provider: "claude", cwd: process.cwd() }); + + const commands = await session.listCommands(); + await session.close(); + + expect(commands).toEqual([ + { + name: "claude-api", + description: "Build, debug, and optimize Claude API apps with this skill.", + argumentHint: "", + kind: "skill", + }, + { + name: "clear", + description: "Start a new session with empty context", + argumentHint: "", + kind: "command", + }, + { + name: "rewind", + description: "Rewind tracked files to a previous user message", + argumentHint: "[user_message_uuid]", + }, + { + name: "taste", + description: "Use when another skill needs the shared standard. (user)", + argumentHint: "", + kind: "skill", + }, + { + name: "usage", + description: "Show the total cost and duration of the current session", + argumentHint: "", + kind: "command", + }, + ]); + }); + test("deletes the persisted session jsonl on close when persistSession=false", async () => { const tmpConfigDir = await fs.mkdtemp(path.join(os.tmpdir(), "paseo-claude-persist-")); const previousConfigDir = process.env.CLAUDE_CONFIG_DIR; diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index 2b9203948..307f33094 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -302,6 +302,18 @@ const REWIND_COMMAND: AgentSlashCommand = { description: "Rewind tracked files to a previous user message", argumentHint: "[user_message_uuid]", }; +const CLAUDE_ROOT_ONLY_COMMANDS = new Set([ + "clear", + "compact", + "context", + "debug", + "extra-usage", + "heapdump", + "init", + "loop", + "schedule", + "usage", +]); const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]"; const INTERRUPT_PLACEHOLDER_PATTERN = /^\[Request interrupted by user(?:[^\]]*)\]$/; const NO_RESPONSE_REQUESTED_PLACEHOLDER = "No response requested."; @@ -313,6 +325,13 @@ interface SlashCommandInvocation { rawInput: string; } +function classifyClaudeSlashCommand(commandName: string): AgentSlashCommand["kind"] { + // Claude exposes commands and skills as one flat SDK list, without structured source + // metadata. Keep obvious root-only/session controls out of inline autocomplete and + // treat the rest as skills; the worst failure mode is an inert inline suggestion. + return CLAUDE_ROOT_ONLY_COMMANDS.has(commandName) ? "command" : "skill"; +} + type ClaudeAgentConfig = AgentSessionConfig & { provider: "claude" }; export interface ClaudeContentChunk { @@ -2137,7 +2156,7 @@ class ClaudeAgentSession implements AgentSession { name: cmd.name, description: cmd.description, argumentHint: cmd.argumentHint, - kind: "command", + kind: classifyClaudeSlashCommand(cmd.name), }); } }