From d77630154ad08221b920e6792970e3a8ba82e3e1 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 16:39:21 -0500 Subject: [PATCH] Fix required Paperclip skill rows on agent detail --- ui/src/pages/AgentDetail.tsx | 98 +++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index 6b095c76..83ffcee7 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -60,7 +60,7 @@ import { import { Input } from "@/components/ui/input"; import { AgentIcon, AgentIconPicker } from "../components/AgentIconPicker"; import { RunTranscriptView, type TranscriptMode } from "../components/transcript/RunTranscriptView"; -import { isUuidLike, type Agent, type HeartbeatRun, type HeartbeatRunEvent, type AgentRuntimeState, type LiveEvent } from "@paperclipai/shared"; +import { isUuidLike, type Agent, type AgentRuntimeState, type AgentSkillSnapshot, type HeartbeatRun, type HeartbeatRunEvent, type LiveEvent } from "@paperclipai/shared"; import { redactHomePathUserSegments, redactHomePathUserSegmentsInValue } from "@paperclipai/adapter-utils"; import { agentRouteRef } from "../lib/utils"; @@ -253,6 +253,9 @@ export function AgentDetail() { const [actionError, setActionError] = useState(null); const [moreOpen, setMoreOpen] = useState(false); const activeView = urlRunId ? "runs" as AgentDetailView : parseAgentDetailView(urlTab ?? null); + const needsDashboardData = activeView === "dashboard"; + const needsRunData = activeView === "runs" || Boolean(urlRunId); + const shouldLoadHeartbeats = needsDashboardData || needsRunData; const [configDirty, setConfigDirty] = useState(false); const [configSaving, setConfigSaving] = useState(false); const saveConfigActionRef = useRef<(() => void) | null>(null); @@ -282,25 +285,25 @@ export function AgentDetail() { const { data: runtimeState } = useQuery({ queryKey: queryKeys.agents.runtimeState(resolvedAgentId ?? routeAgentRef), queryFn: () => agentsApi.runtimeState(resolvedAgentId!, resolvedCompanyId ?? undefined), - enabled: Boolean(resolvedAgentId), + enabled: Boolean(resolvedAgentId) && needsDashboardData, }); const { data: heartbeats } = useQuery({ queryKey: queryKeys.heartbeats(resolvedCompanyId!, agent?.id ?? undefined), queryFn: () => heartbeatsApi.list(resolvedCompanyId!, agent?.id ?? undefined), - enabled: !!resolvedCompanyId && !!agent?.id, + enabled: !!resolvedCompanyId && !!agent?.id && shouldLoadHeartbeats, }); const { data: allIssues } = useQuery({ queryKey: queryKeys.issues.list(resolvedCompanyId!), queryFn: () => issuesApi.list(resolvedCompanyId!), - enabled: !!resolvedCompanyId, + enabled: !!resolvedCompanyId && needsDashboardData, }); const { data: allAgents } = useQuery({ queryKey: queryKeys.agents.list(resolvedCompanyId!), queryFn: () => agentsApi.list(resolvedCompanyId!), - enabled: !!resolvedCompanyId, + enabled: !!resolvedCompanyId && needsDashboardData, }); const assignedIssues = (allIssues ?? []) @@ -1149,6 +1152,16 @@ function AgentSkillsTab({ agent: Agent; companyId?: string; }) { + type SkillRow = { + id: string; + slug: string; + name: string; + description: string | null; + detail: string | null; + linkTo: string | null; + adapterEntry: AgentSkillSnapshot["entries"][number] | null; + }; + const queryClient = useQueryClient(); const [skillDraft, setSkillDraft] = useState([]); const [lastSavedSkills, setLastSavedSkills] = useState([]); @@ -1210,6 +1223,39 @@ function AgentSkillsTab({ () => new Map((skillSnapshot?.entries ?? []).map((entry) => [entry.name, entry])), [skillSnapshot], ); + const optionalSkillRows = useMemo( + () => + (companySkills ?? []) + .filter((skill) => !adapterEntryByName.get(skill.slug)?.required) + .map((skill) => ({ + id: skill.id, + slug: skill.slug, + name: skill.name, + description: skill.description, + detail: adapterEntryByName.get(skill.slug)?.detail ?? null, + linkTo: `/skills/${skill.id}`, + adapterEntry: adapterEntryByName.get(skill.slug) ?? null, + })), + [adapterEntryByName, companySkills], + ); + const requiredSkillRows = useMemo( + () => + (skillSnapshot?.entries ?? []) + .filter((entry) => entry.required) + .map((entry) => { + const companySkill = companySkillBySlug.get(entry.name); + return { + id: companySkill?.id ?? `required:${entry.name}`, + slug: entry.name, + name: companySkill?.name ?? entry.name, + description: companySkill?.description ?? null, + detail: entry.detail ?? null, + linkTo: companySkill ? `/skills/${companySkill.id}` : null, + adapterEntry: entry, + }; + }), + [companySkillBySlug, skillSnapshot], + ); const desiredOnlyMissingSkills = useMemo( () => skillDraft.filter((slug) => !companySkillBySlug.has(slug)), [companySkillBySlug, skillDraft], @@ -1276,18 +1322,10 @@ function AgentSkillsTab({ ) : ( <> {(() => { - const allSkills = companySkills ?? []; - const optionalSkills = allSkills.filter( - (skill) => !adapterEntryByName.get(skill.slug)?.required, - ); - const requiredSkills = allSkills.filter( - (skill) => adapterEntryByName.get(skill.slug)?.required, - ); - - const renderSkillRow = (skill: (typeof allSkills)[number]) => { - const checked = skillDraft.includes(skill.slug); - const adapterEntry = adapterEntryByName.get(skill.slug); + const renderSkillRow = (skill: SkillRow) => { + const adapterEntry = skill.adapterEntry ?? adapterEntryByName.get(skill.slug); const required = Boolean(adapterEntry?.required); + const checked = required || Boolean(adapterEntry?.desired) || skillDraft.includes(skill.slug); const disabled = required || skillSnapshot?.mode === "unsupported"; const checkbox = (
{skill.name} - - View - + {skill.linkTo ? ( + + View + + ) : null}
{skill.description && ( {skill.description} )} - {adapterEntry?.detail && ( -

{adapterEntry.detail}

+ {skill.detail && ( +

{skill.detail}

)} ); }; - if (allSkills.length === 0) { + if (optionalSkillRows.length === 0 && requiredSkillRows.length === 0) { return (
@@ -1362,20 +1402,20 @@ function AgentSkillsTab({ return ( <> - {optionalSkills.length > 0 && ( + {optionalSkillRows.length > 0 && (
- {optionalSkills.map(renderSkillRow)} + {optionalSkillRows.map(renderSkillRow)}
)} - {requiredSkills.length > 0 && ( + {requiredSkillRows.length > 0 && (
Required by Paperclip
- {requiredSkills.map(renderSkillRow)} + {requiredSkillRows.map(renderSkillRow)}
)}