Files
fusion/packages/dashboard/app/utils/agentSkills.ts
gsxdsm 9a9e591b72 FN-9115: clarify and expand agent skill selection
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>
2026-08-15 23:14:24 -07:00

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] };
}