feat(settings): group agents and skills sidebar by subfolder (#464)
* feat(settings): group agents and skills by subfolder in sidebar - Server: fix getUserAgentPath() to walk subfolders so grouped agent layouts (e.g. agents/business/ceo.md) are correctly resolved - Store: add 'group' field to AgentWithExtras and DiscoveredSkill, parsed from file path at load time - UI: add collapsible SidebarGroup component with localStorage-persisted expand/collapse state - AgentsSidebar: render custom agents grouped by subfolder name - SkillsSidebar: render project/user skills grouped by domain folder - Ungrouped items (flat root) fall through and render normally * fix(settings): normalize group paths and add agent lookup caching parity --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
3cd6d051cb
commit
b2101acfbf
@@ -81,8 +81,26 @@ export type AgentWithExtras = Agent & {
|
||||
native?: boolean;
|
||||
hidden?: boolean;
|
||||
options?: { hidden?: boolean };
|
||||
scope?: AgentScope;
|
||||
/** Subfolder name parsed from file path, e.g. "business", "development" */
|
||||
group?: string;
|
||||
};
|
||||
|
||||
/** Parse the subfolder group name from an agent file path.
|
||||
* e.g. "~/.config/opencode/agents/business/ceo.md" → "business"
|
||||
* e.g. "~/.config/opencode/agents/ceo.md" → undefined
|
||||
*/
|
||||
function parseAgentGroup(path: string | null | undefined): string | undefined {
|
||||
if (!path) return undefined;
|
||||
const normalizedPath = path.replace(/\\/g, '/');
|
||||
const idx = normalizedPath.lastIndexOf('/agents/');
|
||||
if (idx === -1) return undefined;
|
||||
const relative = normalizedPath.substring(idx + '/agents/'.length);
|
||||
const parts = relative.split('/');
|
||||
// parts[0] = group, parts[1] = filename; need at least 2 parts
|
||||
return parts.length > 1 ? parts[0] : undefined;
|
||||
}
|
||||
|
||||
// Helper to check if agent is built-in (handles both SDK 'builtIn' and API 'native')
|
||||
export const isAgentBuiltIn = (agent: Agent): boolean => {
|
||||
const extended = agent as AgentWithExtras & { builtIn?: boolean };
|
||||
@@ -202,12 +220,16 @@ export const useAgentsStore = create<AgentsStore>()(
|
||||
?? sources.json?.scope;
|
||||
}
|
||||
|
||||
// Parse subfolder group from file path
|
||||
const mdPath: string | null | undefined = data.sources?.md?.path;
|
||||
const group = parseAgentGroup(mdPath);
|
||||
|
||||
if (scope === 'project' || scope === 'user') {
|
||||
return { ...agent, scope: scope as AgentScope };
|
||||
return { ...agent, scope: scope as AgentScope, group };
|
||||
}
|
||||
|
||||
// Explicitly set null scope if not found, to clear stale state
|
||||
return { ...agent, scope: undefined };
|
||||
return { ...agent, scope: undefined, group };
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[AgentsStore] Failed to fetch config for agent ${agent.name}:`, err);
|
||||
|
||||
Reference in New Issue
Block a user