From cf4c30ab7b72abe75aac896b9fad76da30fbae2b Mon Sep 17 00:00:00 2001 From: Nikita Ashikhmin Date: Tue, 7 Jul 2026 19:00:15 +0400 Subject: [PATCH] Show skill reads as ACP read tool calls --- src/CodexToolCallMapper.ts | 47 ++++++++++++++++++- .../command-action-events.test.ts | 37 +++++++++++++++ .../data/command-read-skill.json | 25 ++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/CodexACPAgent/data/command-read-skill.json diff --git a/src/CodexToolCallMapper.ts b/src/CodexToolCallMapper.ts index 3e2fd4e7..cb09b227 100644 --- a/src/CodexToolCallMapper.ts +++ b/src/CodexToolCallMapper.ts @@ -427,7 +427,23 @@ export function createCommandActionEvent( ): AcpToolCallEvent { const acpStatus = toAcpStatus(status); switch (commandAction.type) { - case "read": + case "read": { + const skillName = skillNameFromSkillPath(commandAction.path); + if (skillName) { + return { + sessionUpdate: "tool_call", + toolCallId: id, + status: acpStatus, + kind: "read", + title: `Read ${formatSkillName(skillName)} skill`, + locations: [{ path: commandAction.path }], + rawInput: { + type: "skill", + name: skillName, + path: commandAction.path, + }, + }; + } return { sessionUpdate: "tool_call", toolCallId: id, @@ -436,6 +452,7 @@ export function createCommandActionEvent( title: `Read file '${commandAction.path}'`, locations: [{ path: commandAction.path }], }; + } case "search": return { sessionUpdate: "tool_call", @@ -471,6 +488,34 @@ export function createCommandActionEvent( } } +function skillNameFromSkillPath(filePath: string): string | null { + const normalized = filePath.replace(/[\\/]+/g, "/"); + const segments = normalized.split("/").filter(segment => segment.length > 0); + if (segments.at(-1) !== "SKILL.md") { + return null; + } + + const skillDirectory = segments.at(-2); + if (!skillDirectory) { + return null; + } + + const parent = segments.at(-3); + if (parent !== "skills") { + return null; + } + + return skillDirectory; +} + +function formatSkillName(skillName: string): string { + return skillName + .split(/[-_\s]+/g) + .filter(part => part.length > 0) + .map(part => part.length <= 3 ? part.toUpperCase() : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`) + .join(" "); +} + export function commandExecutionUsesTerminalOutput(item: CommandExecutionItem): boolean { const commandAction = item.commandActions.length === 1 ? item.commandActions[0] : undefined; return commandAction === undefined || commandAction.type === "unknown"; diff --git a/src/__tests__/CodexACPAgent/command-action-events.test.ts b/src/__tests__/CodexACPAgent/command-action-events.test.ts index 17140df7..84488297 100644 --- a/src/__tests__/CodexACPAgent/command-action-events.test.ts +++ b/src/__tests__/CodexACPAgent/command-action-events.test.ts @@ -128,6 +128,43 @@ describe('CodexEventHandler - command action events', () => { ); }); + it('should render skill instruction reads as skill read tool calls', async () => { + const readSkillNotification: ServerNotification = { + method: 'item/started', + params: { + threadId: sessionId, + turnId: 'turn-1', + startedAtMs: 0, + item: { + type: 'commandExecution', + id: 'command-read-skill', + command: "sed -n '1,240p' /test/project/.agents/skills/ui-accessibility/SKILL.md", + cwd: '/test/project', + processId: null, + source: 'agent', + status: 'inProgress', + commandActions: [ + { + type: 'read', + command: "sed -n '1,240p' /test/project/.agents/skills/ui-accessibility/SKILL.md", + name: 'SKILL.md', + path: '/test/project/.agents/skills/ui-accessibility/SKILL.md', + }, + ], + aggregatedOutput: null, + exitCode: null, + durationMs: null, + }, + }, + }; + + await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, [readSkillNotification]); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + 'data/command-read-skill.json' + ); + }); + it('should handle search command with query and path', async () => { const searchNotification: ServerNotification = { method: 'item/started', diff --git a/src/__tests__/CodexACPAgent/data/command-read-skill.json b/src/__tests__/CodexACPAgent/data/command-read-skill.json new file mode 100644 index 00000000..28675c8e --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/command-read-skill.json @@ -0,0 +1,25 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "command-read-skill", + "status": "in_progress", + "kind": "read", + "title": "Read UI Accessibility skill", + "locations": [ + { + "path": "/test/project/.agents/skills/ui-accessibility/SKILL.md" + } + ], + "rawInput": { + "type": "skill", + "name": "ui-accessibility", + "path": "/test/project/.agents/skills/ui-accessibility/SKILL.md" + } + } + } + ] +} \ No newline at end of file