diff --git a/src/CodexCommands.ts b/src/CodexCommands.ts index 1d5e86d2..eeb4d259 100644 --- a/src/CodexCommands.ts +++ b/src/CodexCommands.ts @@ -87,6 +87,9 @@ export class CodexCommands { for (const entry of skillsEntries) { for (const skill of entry.skills) { + // A skill the user disabled (`[[skills.config]] enabled = false` in + // ~/.codex/config.toml) is not available, so it should not be advertised. + if (!skill.enabled) continue; const name = `$${skill.name}`; if (commands.has(name)) continue; const description = skill.shortDescription ?? skill.description ?? skill.name; @@ -265,7 +268,7 @@ export class CodexCommands { } case "skills": { const response = await this.runWithProcessCheck(() => this.codexAcpClient.listSkills(this.createSkillsListParams(sessionState))); - const skills = (response?.data ?? []).flatMap(entry => entry.skills); + const skills = (response?.data ?? []).flatMap(entry => entry.skills).filter(skill => skill.enabled); const lines = skills.map(skill => { const description = skill.shortDescription ?? skill.description ?? ""; return description ? `- ${skill.name}: ${description}` : `- ${skill.name}`; diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index d1df6999..c06f1d74 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -1430,6 +1430,42 @@ describe('ACP server test', { timeout: 40_000 }, () => { await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot("data/available-commands-skills.json"); }); + it('should not advertise skills the user disabled', async () => { + const mockFixture = createCodexMockTestFixture(); + const codexAcpAgent = mockFixture.getCodexAcpAgent(); + + vi.spyOn(mockFixture.getCodexAcpClient(), "listSkills").mockResolvedValue({ + data: [{ + cwd: "/workspace", + skills: [{ + name: "build", + description: "Build the project", + shortDescription: "Build", + path: "/workspace", + scope: "user", + enabled: true + }, { + name: "deploy", + description: "Deploy the project", + shortDescription: "Deploy", + path: "/workspace", + scope: "user", + enabled: false + }], + errors: [] + }] + }); + + // @ts-expect-error - exercising private helper + await codexAcpAgent.availableCommands.publish(createTestSessionState({ + sessionId: "session-id", + cwd: "/workspace", + additionalDirectories: ["/workspace/extra"], + })); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot("data/available-commands-skills-disabled.json"); + }); + it('handles builtin slash command locally', async () => { const mockFixture = createCodexMockTestFixture(); const codexAcpAgent = mockFixture.getCodexAcpAgent(); diff --git a/src/__tests__/CodexACPAgent/data/available-commands-skills-disabled.json b/src/__tests__/CodexACPAgent/data/available-commands-skills-disabled.json new file mode 100644 index 00000000..d6c15414 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/available-commands-skills-disabled.json @@ -0,0 +1,91 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "session-id", + "update": { + "sessionUpdate": "available_commands_update", + "availableCommands": [ + { + "name": "plan", + "description": "Turn plan mode on.", + "input": null, + "_meta": { + "commandAction": { + "kind": "setConfigOption", + "configId": "collaboration_mode", + "value": "plan", + "resetValue": "default", + "presentation": "state" + } + } + }, + { + "name": "mcp", + "description": "List configured Model Context Protocol (MCP) tools.", + "input": null + }, + { + "name": "skills", + "description": "List available skills.", + "input": null + }, + { + "name": "status", + "description": "Display session configuration and token usage.", + "input": null + }, + { + "name": "review", + "description": "Review uncommitted changes, or review with custom instructions.", + "input": { + "hint": "optional review instructions" + } + }, + { + "name": "review-branch", + "description": "Review changes relative to a base branch.", + "input": { + "hint": "branch name" + } + }, + { + "name": "review-commit", + "description": "Review a specific commit.", + "input": { + "hint": "commit sha" + } + }, + { + "name": "compact", + "description": "Summarize conversation to avoid hitting the context limit.", + "input": null + }, + { + "name": "goal", + "description": "Set a goal to keep pursuing.", + "input": { + "hint": "[|clear|pause|resume]" + }, + "_meta": { + "commandAction": { + "kind": "prefixPrompt", + "presentation": "state" + } + } + }, + { + "name": "logout", + "description": "Sign out of Codex. This option is available when you are logged in via ChatGPT.", + "input": null + }, + { + "name": "$build", + "description": "Build", + "input": null + } + ] + } + } + ] +} \ No newline at end of file