From b05983acc2d3bb1afed2e6635bfcb54e519178b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=AC=E9=A3=8E=E5=90=B9=E9=9B=A8?= <2686367411@qq.com> Date: Tue, 10 Nov 2026 22:02:34 +0800 Subject: [PATCH 1/2] fix(chat): enable disk skills in slash menu for custom ACP agents Custom ACP agents (e.g. user-configured Antigravity instances, or custom agents with declared skill directories) do not advertise skills as ACP commands; their skills live on disk. Previously diskSkillTrigger returned null for custom agents, preventing skills from being scanned and displayed in the slash menu. Include custom agents in diskSkillTrigger so that their disk skills are scanned and listed under '/' in the composer. --- .../composer/invocation-reference.test.ts | 10 ++++++++- .../chat/composer/invocation-reference.ts | 21 ++++++++++++++----- src/components/chat/message-input.test.tsx | 21 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/components/chat/composer/invocation-reference.test.ts b/src/components/chat/composer/invocation-reference.test.ts index ade3877ac7..0baf35c074 100644 --- a/src/components/chat/composer/invocation-reference.test.ts +++ b/src/components/chat/composer/invocation-reference.test.ts @@ -130,8 +130,16 @@ describe("diskSkillTrigger", () => { expect(diskSkillTrigger("open_code")).toBe("/") }) + // Custom ACP agents (e.g. user-configured Antigravity, or agents with declared + // skill directories) do not advertise skills as ACP commands; their skills + // live on disk and are invoked via `/`. + it("lists custom ACP agents' disk skills under `/`", () => { + expect(diskSkillTrigger("custom:antigravity-b")).toBe("/") + expect(diskSkillTrigger("custom:my-agent")).toBe("/") + }) + it("scans nothing for an agent that advertises its skills as commands", () => { - for (const agent of ["claude_code", "kimi_code", "gemini", "custom:x"]) { + for (const agent of ["claude_code", "kimi_code", "gemini"]) { expect(diskSkillTrigger(agent)).toBeNull() } expect(diskSkillTrigger(null)).toBeNull() diff --git a/src/components/chat/composer/invocation-reference.ts b/src/components/chat/composer/invocation-reference.ts index 977df932aa..745d36c203 100644 --- a/src/components/chat/composer/invocation-reference.ts +++ b/src/components/chat/composer/invocation-reference.ts @@ -1,8 +1,9 @@ import type { KnownInvocations } from "@/lib/invocation-token" -import type { - AgentSkillItem, - AgentType, - AvailableCommandInfo, +import { + isCustomAgentType, + type AgentSkillItem, + type AgentType, + type AvailableCommandInfo, } from "@/lib/types" import type { ReferenceAttrs } from "./types" @@ -36,12 +37,22 @@ export type InvocationPrefix = "/" | "$" * configured and MCP-prompt ones), while it still loads every skill, names * each to the model and gives the model a `skill` tool to load one by id. A * `/` the user sends reaches the model as written. + * - Custom ACP agents: `/`, the same way. Custom agents (including user-configured + * Antigravity instances or agents with a declared skill store) do not advertise + * skills as ACP commands; their skills live on disk and are invoked via `/`. */ export function diskSkillTrigger( agentType: AgentType | null | undefined ): InvocationPrefix | null { + if (!agentType) return null if (agentType === "codex") return "$" - if (agentType === "antigravity" || agentType === "open_code") return "/" + if ( + agentType === "antigravity" || + agentType === "open_code" || + isCustomAgentType(agentType) + ) { + return "/" + } return null } diff --git a/src/components/chat/message-input.test.tsx b/src/components/chat/message-input.test.tsx index 1391fb4c23..3cef470850 100644 --- a/src/components/chat/message-input.test.tsx +++ b/src/components/chat/message-input.test.tsx @@ -1970,6 +1970,27 @@ describe("MessageInput slash menu for skills an agent does not advertise", () => expect(agentSkills).toHaveBeenCalledWith("antigravity", null) }) + it("lists custom ACP agents' skills from disk after their commands", async () => { + agentSkills.mockReturnValue([DEPLOY, SHADOWED]) + await mountAndType({ + agentType: "custom:antigravity-b", + availableCommands: COMMANDS, + }) + + const menu = await screen.findByTestId("slash-menu") + const rows = within(menu) + .getAllByRole("button") + .map((row) => row.textContent) + expect(rows).toEqual([ + expect.stringMatching(/^\/plan/), + expect.stringMatching(/^\/logout/), + expect.stringMatching(/^\/usage/), + "DeployShip the build", + ]) + expect(within(menu).queryByText("Plan skill")).toBeNull() + expect(agentSkills).toHaveBeenCalledWith("custom:antigravity-b", null) + }) + it("inserts a picked Antigravity skill as its `/` token", async () => { agentSkills.mockReturnValue([DEPLOY]) const { editor } = await mountAndType( From e15d37b61e3da261de5931138f7abdca34a49f59 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Tue, 10 Nov 2026 22:18:08 +0800 Subject: [PATCH 2/2] docs(chat): describe custom agents' declared disk skills in the composer menus The disk-skill trigger now covers custom ACP agents, but the comments in the chat composer and the automation editor still named only Antigravity and OpenCode. Say that a custom agent's scan follows the skill directories its user declared, and that a skill it also advertises as a command is dropped by skillsForMenu. --- src/components/automations/composer-invocations.tsx | 2 +- src/components/chat/composer/invocation-reference.ts | 11 ++++++++--- src/components/chat/message-input.tsx | 8 +++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/automations/composer-invocations.tsx b/src/components/automations/composer-invocations.tsx index 7c191145e0..45ef7316a0 100644 --- a/src/components/automations/composer-invocations.tsx +++ b/src/components/automations/composer-invocations.tsx @@ -87,7 +87,7 @@ export interface ComposerInvocations { * `isExternalMenuOpen`/`onExternalMenuKeyDown` escape hatch; a selected item * becomes a badge that serializes to its literal `/cmd` / `$skill` token on save. * Antigravity's and OpenCode's skills, which their servers never advertise, - * join their commands under `/`. + * and a custom agent's declared skills join their commands under `/`. */ export function useComposerInvocations({ editorRef, diff --git a/src/components/chat/composer/invocation-reference.ts b/src/components/chat/composer/invocation-reference.ts index 745d36c203..65fcd9c287 100644 --- a/src/components/chat/composer/invocation-reference.ts +++ b/src/components/chat/composer/invocation-reference.ts @@ -37,9 +37,14 @@ export type InvocationPrefix = "/" | "$" * configured and MCP-prompt ones), while it still loads every skill, names * each to the model and gives the model a `skill` tool to load one by id. A * `/` the user sends reaches the model as written. - * - Custom ACP agents: `/`, the same way. Custom agents (including user-configured - * Antigravity instances or agents with a declared skill store) do not advertise - * skills as ACP commands; their skills live on disk and are invoked via `/`. + * - Custom ACP agents (`custom:`): `/`. codeg cannot tell whether an + * arbitrary ACP server advertises its skills (Antigravity's + * `agy_acp_server` run as a custom agent never does), so the scan follows the + * user's own declaration instead: a custom agent that declares a dedicated + * skills directory or the shared `.agents/skills` store lists those skills, + * and one that declares neither gets `supported: false` and an empty list. A + * declared skill the agent also advertises as a command is dropped by + * {@link skillsForMenu}, so it is not listed twice. */ export function diskSkillTrigger( agentType: AgentType | null | undefined diff --git a/src/components/chat/message-input.tsx b/src/components/chat/message-input.tsx index eaf650c401..519b3ab593 100644 --- a/src/components/chat/message-input.tsx +++ b/src/components/chat/message-input.tsx @@ -381,9 +381,11 @@ export function MessageInput({ // Skills read from disk feed the menu only for an agent that does not // advertise them itself: Codex under `$` (its native command set is very // small), Antigravity and OpenCode under `/` beside their commands (their - // ACP servers list only commands, never a skill). Every other agent surfaces - // its skills through ACP `availableCommands`, so a disk scan would only - // repeat them — skip it. + // ACP servers list only commands, never a skill), and custom agents under `/` + // from the skill directories their user declared (codeg cannot know what an + // arbitrary ACP server advertises). Every other built-in agent surfaces its + // skills through ACP `availableCommands`, so a disk scan would only repeat + // them — skip it. const skillTrigger = diskSkillTrigger(agentType) // Pass the working dir so we see both global skills and folder-scoped // project skills (e.g. `{folder}/.codex/skills`). Without this, users