Clarify automatic skill availability while making forced-reading selections easier to manage. - Replace the skill picker with a searchable multi-select checkbox list and explicit loading, error, empty, and unavailable states. - Label forced, disabled, and undiscovered skills consistently across agent detail and list views, including a clear None state. - Update localized guidance, documentation, regression coverage, and the published package changeset. Files changed: .changeset/fn-9115-skills-ui.md | 7 + docs/dashboard-guide.md | 4 + .../agent-detail-settings-theme-styling.test.ts | 3 +- .../dashboard/app/components/AgentDetailView.css | 7 +- .../dashboard/app/components/AgentDetailView.tsx | 35 ++- packages/dashboard/app/components/AgentsView.css | 4 + packages/dashboard/app/components/AgentsView.tsx | 13 +- .../dashboard/app/components/NewAgentDialog.tsx | 5 +- .../dashboard/app/components/SkillMultiselect.css | 258 +++------------------ .../dashboard/app/components/SkillMultiselect.tsx | 178 +++++--------- .../AgentDetailView.mobile-scroll.test.tsx | 4 +- .../AgentDetailView.skills-procedure.test.tsx | 59 +++-- .../app/components/__tests__/AgentsView.test.tsx | 19 +- .../components/__tests__/SkillMultiselect.test.tsx | 81 ++++--- .../__tests__/useDiscoveredSkillsCache.test.ts | 16 ++ .../app/hooks/useDiscoveredSkillsCache.ts | 13 +- .../app/utils/__tests__/agentSkills.test.ts | 28 +++ packages/dashboard/app/utils/agentSkills.ts | 69 +++++- packages/i18n/locales/en/app.json | 21 +- packages/i18n/locales/es/app.json | 21 +- packages/i18n/locales/fr/app.json | 21 +- packages/i18n/locales/ko/app.json | 21 +- packages/i18n/locales/pt-BR/app.json | 21 +- packages/i18n/locales/zh-CN/app.json | 21 +- packages/i18n/locales/zh-TW/app.json | 21 +- 25 files changed, 493 insertions(+), 457 deletions(-) Fusion-Task-Id: FN-9115 Fusion-Task-Lineage: 18229b87-d0dc-41e7-9333-2d12df852486 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import type { DiscoveredSkill } from "../api";
|
|
|
|
const SKILL_PATH_LABEL_PATTERN = /(?:^|\/)skills\/([^/]+)\/SKILL\.md$/i;
|
|
|
|
export type AgentSkillState = "auto-available" | "disabled" | "unknown" | "pending";
|
|
|
|
export interface AgentSkillClassification {
|
|
state: AgentSkillState;
|
|
forced: boolean;
|
|
labelKey: string;
|
|
defaultLabel: string;
|
|
titleKey: string;
|
|
defaultTitle: string;
|
|
}
|
|
|
|
/**
|
|
* Formats stored skill IDs consistently across agent display surfaces.
|
|
*/
|
|
export function formatAgentSkillBadgeLabel(skillId: string): string {
|
|
const trimmedSkillId = skillId.trim();
|
|
if (!trimmedSkillId) return skillId;
|
|
|
|
const match = trimmedSkillId.match(SKILL_PATH_LABEL_PATTERN);
|
|
return match?.[1] ?? trimmedSkillId;
|
|
}
|
|
|
|
/**
|
|
* Classifies a stored agent skill against the canonical discovery response.
|
|
* A missing response is pending rather than unknown because discovery may still load or retry.
|
|
*/
|
|
export function classifyAgentSkill(
|
|
skillId: string,
|
|
discovered: DiscoveredSkill[] | null,
|
|
options: { forced: boolean },
|
|
): AgentSkillClassification {
|
|
if (discovered === null) {
|
|
return classification("pending", options.forced);
|
|
}
|
|
|
|
const skill = discovered.find((candidate) => candidate.id === skillId);
|
|
return classification(skill ? (skill.enabled ? "auto-available" : "disabled") : "unknown", options.forced);
|
|
}
|
|
|
|
function classification(state: AgentSkillState, forced: boolean): AgentSkillClassification {
|
|
const labels: Record<AgentSkillState, Omit<AgentSkillClassification, "state" | "forced">> = {
|
|
"auto-available": {
|
|
labelKey: "skills.autoAvailable",
|
|
defaultLabel: "Auto-available",
|
|
titleKey: "skills.autoAvailableTitle",
|
|
defaultTitle: "Enabled skills are available automatically.",
|
|
},
|
|
disabled: {
|
|
labelKey: "skills.disabledSkill",
|
|
defaultLabel: "Disabled",
|
|
titleKey: "skills.disabledSkillTitle",
|
|
defaultTitle: "Disabled skills are not delivered to sessions, even when forced.",
|
|
},
|
|
unknown: {
|
|
labelKey: "skills.notDiscovered",
|
|
defaultLabel: "Not discovered",
|
|
titleKey: "skills.notDiscoveredTitle",
|
|
defaultTitle: "This stored skill is no longer discovered by the project.",
|
|
},
|
|
pending: {
|
|
labelKey: "skills.skillStatePending",
|
|
defaultLabel: "Checking availability",
|
|
titleKey: "skills.skillStatePendingTitle",
|
|
defaultTitle: "Skill discovery is loading or could not be refreshed.",
|
|
},
|
|
};
|
|
return { state, forced, ...labels[state] };
|
|
}
|