From 527fcfc2884a7d82a85eb7df8402f47102c73e05 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Wed, 24 Jun 2026 05:46:33 +1000 Subject: [PATCH] feat(ui): use acp method to load skills (#9959) --- ui/desktop/src/acp/sources.ts | 43 +++++++++++++++++++ .../src/components/skills/SkillsView.tsx | 17 +++----- 2 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 ui/desktop/src/acp/sources.ts diff --git a/ui/desktop/src/acp/sources.ts b/ui/desktop/src/acp/sources.ts new file mode 100644 index 000000000..590886e31 --- /dev/null +++ b/ui/desktop/src/acp/sources.ts @@ -0,0 +1,43 @@ +import type { SourceEntry, SourceType } from '@aaif/goose-sdk'; +import { getAcpClient } from './acpConnection'; + +const SKILL_SOURCE_TYPES: SourceType[] = ['skill', 'builtinSkill']; +const inFlightSkillSourceLoads = new Map>(); + +export async function listSkillSources(projectDir: string): Promise { + const inFlightLoad = inFlightSkillSourceLoads.get(projectDir); + if (inFlightLoad) { + return inFlightLoad; + } + + const load = loadSkillSources(projectDir); + inFlightSkillSourceLoads.set(projectDir, load); + + try { + return await load; + } finally { + if (inFlightSkillSourceLoads.get(projectDir) === load) { + inFlightSkillSourceLoads.delete(projectDir); + } + } +} + +async function loadSkillSources(projectDir: string): Promise { + const client = await getAcpClient(); + const responses = await Promise.all( + SKILL_SOURCE_TYPES.map((type) => + client.goose.sourcesList_unstable({ + type, + projectDir, + }) + ) + ); + + return responses + .flatMap((response) => response.sources) + .sort( + (a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) || + a.path.localeCompare(b.path) + ); +} diff --git a/ui/desktop/src/components/skills/SkillsView.tsx b/ui/desktop/src/components/skills/SkillsView.tsx index 35ef1dd16..fdeb86831 100644 --- a/ui/desktop/src/components/skills/SkillsView.tsx +++ b/ui/desktop/src/components/skills/SkillsView.tsx @@ -5,12 +5,12 @@ import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { Skeleton } from '../ui/skeleton'; import { MainPanelLayout } from '../Layout/MainPanelLayout'; -import { getSlashCommands } from '../../api'; import { errorMessage } from '../../utils/conversionUtils'; import { getInitialWorkingDir } from '../../utils/workingDir'; import { defineMessages, useIntl } from '../../i18n'; import { SearchView } from '../conversation/SearchView'; import { getSearchShortcutText } from '../../utils/keyboardShortcuts'; +import { listSkillSources } from '../../acp/sources'; const i18n = defineMessages({ errorLoadingSkills: { @@ -118,16 +118,11 @@ export default function SkillsView() { setShowSkeleton(true); setShowContent(false); setError(null); - const response = await getSlashCommands({ - query: { working_dir: getInitialWorkingDir() }, - throwOnError: true, - }); - const skillEntries: SkillEntry[] = (response.data?.commands ?? []) - .filter((cmd) => cmd.command_type === 'Skill') - .map((cmd) => ({ - name: cmd.command, - description: cmd.help, - })); + const sources = await listSkillSources(getInitialWorkingDir()); + const skillEntries: SkillEntry[] = sources.map((source) => ({ + name: source.name, + description: source.description, + })); setSkills(skillEntries); } catch (err) { setError(errorMessage(err, 'Failed to load skills'));