diff --git a/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx b/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx index 5974d7e8..2baf3608 100644 --- a/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx +++ b/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx @@ -130,6 +130,14 @@ const getToolFilePath = (activity: TurnActivityPart): string | null => { return typeof filePath === 'string' && filePath.trim().length > 0 ? filePath : null; }; +const getToolSkillDirectory = (activity: TurnActivityPart): string | null => { + const part = activity.part as ToolPartType; + const state = part.state as { metadata?: Record } | undefined; + const dir = state?.metadata?.dir; + + return typeof dir === 'string' && dir.trim().length > 0 ? dir : null; +}; + const toTodoStatusKey = (value: unknown): 'pending' | 'in_progress' | 'completed' | 'cancelled' | null => { if (typeof value !== 'string') { return null; @@ -329,6 +337,15 @@ const resolveAbsolutePath = (currentDirectory: string, filePath: string): string return normalizedDirectory.endsWith('/') ? `${normalizedDirectory}${normalizedPath}` : `${normalizedDirectory}/${normalizedPath}`; }; +const resolveSkillFilePath = (skillPathOrDir: string): string => { + const normalizedPath = trimTrailingSlashes(normalizePathValue(skillPathOrDir)); + if (!normalizedPath) { + return ''; + } + + return normalizedPath.toLowerCase().endsWith('/skill.md') ? normalizedPath : `${normalizedPath}/SKILL.md`; +}; + const getContextDirectoryForPath = (currentDirectory: string, absolutePath: string): string => { const normalizedDirectory = normalizePathValue(currentDirectory); if (normalizedDirectory) { @@ -639,6 +656,24 @@ const StaticToolRowInner: React.FC<{ return descs; }, [activities]); + const skillEntries = React.useMemo(() => { + if (toolName.toLowerCase() !== 'skill') return [] as Array<{ name: string; path: string }>; + + const entries: Array<{ name: string; path: string }> = []; + for (const activity of activities) { + const name = getToolShortDescription(activity); + if (!name) continue; + + const skill = skillByName.get(name); + const rawPath = skill?.path || getToolSkillDirectory(activity); + const path = rawPath ? resolveSkillFilePath(rawPath) : ''; + if (!path || entries.some((entry) => entry.name === name && entry.path === path)) continue; + entries.push({ name, path }); + } + + return entries; + }, [activities, skillByName, toolName]); + const readFileEntries = React.useMemo(() => { if (!isReadGroup) return [] as Array<{ path: string; displayPath: string; offset?: number }>; @@ -675,14 +710,13 @@ const StaticToolRowInner: React.FC<{ uiStore.openContextFile(contextDirectory, absolutePath); }, [currentDirectory, runtime]); - const handleSkillClick = React.useCallback((skillName: string) => { - const skill = skillByName.get(skillName); - if (!skill?.path) { + const handleSkillClick = React.useCallback((skillPath: string) => { + if (!skillPath) { return; } const uiStore = useUIStore.getState(); - uiStore.openContextFile(currentDirectory || getContextDirectoryForPath('', skill.path), skill.path); - }, [currentDirectory, skillByName]); + uiStore.openContextFile(currentDirectory || getContextDirectoryForPath('', skillPath), skillPath); + }, [currentDirectory]); const normalizedToolName = toolName.toLowerCase(); const isSearchGroup = normalizedToolName === 'grep' @@ -763,21 +797,21 @@ const StaticToolRowInner: React.FC<{ )) : null} - {isSkillGroup && descriptions.length > 0 - ? descriptions.map((skillName, index) => ( + {isSkillGroup && skillEntries.length > 0 + ? skillEntries.map((entry, index) => ( )) : null}