[nexus] feat(19-03): update API client to use agentId instead of agentSkillsDir

- Add AgentSkillEntry type with skillId, source, installedAt fields
- install() now sends agentId in body not agentSkillsDir
- uninstall() new function that sends agentId as query param
- rollback() now sends agentId in body not agentSkillsDir
- skillGroups.assignGroup/removeGroup no longer accept agentSkillsDir param
- listAgentSkills now returns AgentSkillEntry[] not string[]
- Fix AgentDetail.tsx callers and entry rendering for new AgentSkillEntry type
- Fix SkillDetail.tsx callers to use agentId param
This commit is contained in:
Mikkel Georgsen 2026-04-01 11:35:40 +02:00 committed by Nexus Dev
parent db83eb2a00
commit 305ea411da
4 changed files with 36 additions and 31 deletions

View file

@ -1,4 +1,5 @@
import { api, ApiError } from "./client"; import { api, ApiError } from "./client";
import type { AgentSkillEntry } from "./skillRegistry";
export type SkillGroupRow = { export type SkillGroupRow = {
id: string; id: string;
@ -65,18 +66,15 @@ export const skillGroupsApi = {
listAgentGroups: (agentId: string) => listAgentGroups: (agentId: string) =>
api.get<SkillGroupRow[]>(`/skill-registry/agents/${agentId}/groups`), api.get<SkillGroupRow[]>(`/skill-registry/agents/${agentId}/groups`),
assignGroup: (agentId: string, groupId: string, agentSkillsDir: string) => assignGroup: (agentId: string, groupId: string) =>
api.post<AssignResult>(`/skill-registry/agents/${agentId}/groups`, { api.post<AssignResult>(`/skill-registry/agents/${agentId}/groups`, {
groupId, groupId,
agentSkillsDir,
}), }),
removeGroup: async (agentId: string, groupId: string, agentSkillsDir: string): Promise<void> => { removeGroup: async (agentId: string, groupId: string): Promise<void> => {
const res = await fetch(`/api/skill-registry/agents/${agentId}/groups/${groupId}`, { const res = await fetch(`/api/skill-registry/agents/${agentId}/groups/${groupId}`, {
method: "DELETE", method: "DELETE",
credentials: "include", credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ agentSkillsDir }),
}); });
if (!res.ok && res.status !== 204) { if (!res.ok && res.status !== 204) {
const errorBody = await res.json().catch(() => null); const errorBody = await res.json().catch(() => null);
@ -89,5 +87,5 @@ export const skillGroupsApi = {
}, },
listAgentSkills: (agentId: string) => listAgentSkills: (agentId: string) =>
api.get<string[]>(`/skill-registry/agents/${agentId}/skills`), api.get<AgentSkillEntry[]>(`/skill-registry/agents/${agentId}/skills`),
}; };

View file

@ -1,5 +1,11 @@
import { api } from "./client"; import { api } from "./client";
export type AgentSkillEntry = {
skillId: string;
source: "managed" | "native";
installedAt: number;
};
export type SkillListItem = { export type SkillListItem = {
id: string; id: string;
name: string; name: string;
@ -50,10 +56,12 @@ export const skillRegistryApi = {
api.get<SkillVersion[]>(`${skillPath(skillId)}/versions`), api.get<SkillVersion[]>(`${skillPath(skillId)}/versions`),
fetch: () => fetch: () =>
api.post<{ fetched: number; errors: string[] }>("/skill-registry/fetch", {}), api.post<{ fetched: number; errors: string[] }>("/skill-registry/fetch", {}),
install: (skillId: string, agentSkillsDir: string) => install: (skillId: string, agentId: string) =>
api.post(`${skillPath(skillId)}/install`, { agentSkillsDir }), api.post(`${skillPath(skillId)}/install`, { agentId }),
rollback: (skillId: string, versionId: string, agentSkillsDir: string) => uninstall: (skillId: string, agentId: string) =>
api.post(`${skillPath(skillId)}/rollback`, { versionId, agentSkillsDir }), api.delete(`${skillPath(skillId)}?agentId=${encodeURIComponent(agentId)}`),
rollback: (skillId: string, versionId: string, agentId: string) =>
api.post(`${skillPath(skillId)}/rollback`, { versionId, agentId }),
remove: (skillId: string) => remove: (skillId: string) =>
api.delete(skillPath(skillId)), api.delete(skillPath(skillId)),
getRatings: (skillId: string) => getRatings: (skillId: string) =>

View file

@ -2440,7 +2440,7 @@ function AgentSkillsTab({
// Group mutations // Group mutations
const assignGroupMut = useMutation({ const assignGroupMut = useMutation({
mutationFn: ({ groupId }: { groupId: string }) => mutationFn: ({ groupId }: { groupId: string }) =>
skillGroupsApi.assignGroup(agent.id, groupId, ""), skillGroupsApi.assignGroup(agent.id, groupId),
onSuccess: () => { onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentGroups(agent.id) }); void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentGroups(agent.id) });
void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentSkills(agent.id) }); void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentSkills(agent.id) });
@ -2450,7 +2450,7 @@ function AgentSkillsTab({
const removeGroupMut = useMutation({ const removeGroupMut = useMutation({
mutationFn: ({ groupId }: { groupId: string }) => mutationFn: ({ groupId }: { groupId: string }) =>
skillGroupsApi.removeGroup(agent.id, groupId, ""), skillGroupsApi.removeGroup(agent.id, groupId),
onSuccess: () => { onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentGroups(agent.id) }); void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentGroups(agent.id) });
void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentSkills(agent.id) }); void queryClient.invalidateQueries({ queryKey: queryKeys.skillGroups.agentSkills(agent.id) });
@ -2735,9 +2735,9 @@ function AgentSkillsTab({
) : ( ) : (
<ScrollArea className="max-h-[300px] pt-2"> <ScrollArea className="max-h-[300px] pt-2">
<ul className="space-y-1"> <ul className="space-y-1">
{(agentEffectiveSkillsQuery.data ?? []).map((skillId) => ( {(agentEffectiveSkillsQuery.data ?? []).map((entry) => (
<li key={skillId} className="text-sm text-muted-foreground font-mono"> <li key={entry.skillId} className="text-sm text-muted-foreground font-mono">
{skillId} {entry.skillId}
</li> </li>
))} ))}
</ul> </ul>

View file

@ -95,7 +95,7 @@ export function SkillDetail() {
const [installDialog, setInstallDialog] = useState<{ const [installDialog, setInstallDialog] = useState<{
skillId: string; skillId: string;
isUpdate: boolean; isUpdate: boolean;
agentSkillsDir?: string; agentId?: string;
} | null>(null); } | null>(null);
// Uninstall confirmation dialog state // Uninstall confirmation dialog state
@ -149,8 +149,8 @@ export function SkillDetail() {
}; };
const installMutation = useMutation({ const installMutation = useMutation({
mutationFn: ({ agentSkillsDir }: { agentSkillsDir: string }) => mutationFn: ({ agentId }: { agentId: string }) =>
skillRegistryApi.install(skillId, agentSkillsDir), skillRegistryApi.install(skillId, agentId),
onSuccess: () => { onSuccess: () => {
invalidateSkill(); invalidateSkill();
setInstallDialog(null); setInstallDialog(null);
@ -162,8 +162,8 @@ export function SkillDetail() {
}); });
const updateMutation = useMutation({ const updateMutation = useMutation({
mutationFn: ({ agentSkillsDir }: { agentSkillsDir: string }) => mutationFn: ({ agentId }: { agentId: string }) =>
skillRegistryApi.install(skillId, agentSkillsDir), skillRegistryApi.install(skillId, agentId),
onSuccess: () => { onSuccess: () => {
invalidateSkill(); invalidateSkill();
setInstallDialog(null); setInstallDialog(null);
@ -175,8 +175,8 @@ export function SkillDetail() {
}); });
const rollbackMutation = useMutation({ const rollbackMutation = useMutation({
mutationFn: ({ versionId, agentSkillsDir }: { versionId: string; agentSkillsDir: string }) => mutationFn: ({ versionId, agentId }: { versionId: string; agentId: string }) =>
skillRegistryApi.rollback(skillId, versionId, agentSkillsDir), skillRegistryApi.rollback(skillId, versionId, agentId),
onSuccess: () => { onSuccess: () => {
invalidateSkill(); invalidateSkill();
pushToast({ title: "Rolled back to previous version", tone: "success" }); pushToast({ title: "Rolled back to previous version", tone: "success" });
@ -596,16 +596,15 @@ export function SkillDetail() {
</DialogHeader> </DialogHeader>
<div className="py-2"> <div className="py-2">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Agent skills directory is required to install. Enter the path to the agent&apos;s Enter the agent ID to install this skill.
skills directory.
</p> </p>
<input <input
type="text" type="text"
className="mt-2 w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-ring" className="mt-2 w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
placeholder="/path/to/agent/skills" placeholder="Agent ID"
value={installDialog?.agentSkillsDir ?? ""} value={installDialog?.agentId ?? ""}
onChange={(e) => onChange={(e) =>
setInstallDialog((d) => d ? { ...d, agentSkillsDir: e.target.value } : null) setInstallDialog((d) => d ? { ...d, agentId: e.target.value } : null)
} }
/> />
</div> </div>
@ -614,13 +613,13 @@ export function SkillDetail() {
Cancel Cancel
</Button> </Button>
<Button <Button
disabled={!installDialog?.agentSkillsDir || isMutating} disabled={!installDialog?.agentId || isMutating}
onClick={() => { onClick={() => {
if (!installDialog?.agentSkillsDir) return; if (!installDialog?.agentId) return;
if (installDialog.isUpdate) { if (installDialog.isUpdate) {
updateMutation.mutate({ agentSkillsDir: installDialog.agentSkillsDir }); updateMutation.mutate({ agentId: installDialog.agentId });
} else { } else {
installMutation.mutate({ agentSkillsDir: installDialog.agentSkillsDir }); installMutation.mutate({ agentId: installDialog.agentId });
} }
}} }}
> >