fix: open loaded skill files from tool messages
Open SKILL.md from skill tool metadata Keep click behavior working before skills store loads
This commit is contained in:
@@ -130,6 +130,14 @@ const getToolFilePath = (activity: TurnActivityPart): string | null => {
|
|||||||
return typeof filePath === 'string' && filePath.trim().length > 0 ? filePath : 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<string, unknown> } | 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 => {
|
const toTodoStatusKey = (value: unknown): 'pending' | 'in_progress' | 'completed' | 'cancelled' | null => {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
@@ -329,6 +337,15 @@ const resolveAbsolutePath = (currentDirectory: string, filePath: string): string
|
|||||||
return normalizedDirectory.endsWith('/') ? `${normalizedDirectory}${normalizedPath}` : `${normalizedDirectory}/${normalizedPath}`;
|
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 getContextDirectoryForPath = (currentDirectory: string, absolutePath: string): string => {
|
||||||
const normalizedDirectory = normalizePathValue(currentDirectory);
|
const normalizedDirectory = normalizePathValue(currentDirectory);
|
||||||
if (normalizedDirectory) {
|
if (normalizedDirectory) {
|
||||||
@@ -639,6 +656,24 @@ const StaticToolRowInner: React.FC<{
|
|||||||
return descs;
|
return descs;
|
||||||
}, [activities]);
|
}, [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(() => {
|
const readFileEntries = React.useMemo(() => {
|
||||||
if (!isReadGroup) return [] as Array<{ path: string; displayPath: string; offset?: number }>;
|
if (!isReadGroup) return [] as Array<{ path: string; displayPath: string; offset?: number }>;
|
||||||
|
|
||||||
@@ -675,14 +710,13 @@ const StaticToolRowInner: React.FC<{
|
|||||||
uiStore.openContextFile(contextDirectory, absolutePath);
|
uiStore.openContextFile(contextDirectory, absolutePath);
|
||||||
}, [currentDirectory, runtime]);
|
}, [currentDirectory, runtime]);
|
||||||
|
|
||||||
const handleSkillClick = React.useCallback((skillName: string) => {
|
const handleSkillClick = React.useCallback((skillPath: string) => {
|
||||||
const skill = skillByName.get(skillName);
|
if (!skillPath) {
|
||||||
if (!skill?.path) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uiStore = useUIStore.getState();
|
const uiStore = useUIStore.getState();
|
||||||
uiStore.openContextFile(currentDirectory || getContextDirectoryForPath('', skill.path), skill.path);
|
uiStore.openContextFile(currentDirectory || getContextDirectoryForPath('', skillPath), skillPath);
|
||||||
}, [currentDirectory, skillByName]);
|
}, [currentDirectory]);
|
||||||
|
|
||||||
const normalizedToolName = toolName.toLowerCase();
|
const normalizedToolName = toolName.toLowerCase();
|
||||||
const isSearchGroup = normalizedToolName === 'grep'
|
const isSearchGroup = normalizedToolName === 'grep'
|
||||||
@@ -763,21 +797,21 @@ const StaticToolRowInner: React.FC<{
|
|||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
{isSkillGroup && descriptions.length > 0
|
{isSkillGroup && skillEntries.length > 0
|
||||||
? descriptions.map((skillName, index) => (
|
? skillEntries.map((entry, index) => (
|
||||||
<button
|
<button
|
||||||
key={`${skillName}-${index}`}
|
key={`${entry.name}-${entry.path}-${index}`}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
handleSkillClick(skillName);
|
handleSkillClick(entry.path);
|
||||||
}}
|
}}
|
||||||
className="min-w-0 flex-1 truncate whitespace-nowrap typography-meta leading-5 text-left hover:opacity-90"
|
className="min-w-0 flex-1 truncate whitespace-nowrap typography-meta leading-5 text-left hover:opacity-90"
|
||||||
style={{ color: 'var(--tools-description)' }}
|
style={{ color: 'var(--tools-description)' }}
|
||||||
title={skillName}
|
title={entry.path}
|
||||||
>
|
>
|
||||||
{skillName}
|
{entry.name}
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
|
|||||||
Reference in New Issue
Block a user