--- phase: 19-adapter-aware-install-uninstall plan: "03" type: execute wave: 2 depends_on: ["19-01"] files_modified: - ui/src/api/skillRegistry.ts - ui/src/pages/SkillBrowser.tsx - ui/src/components/SkillCard.tsx autonomous: true requirements: [HERM-01, HERM-02, HERM-03] must_haves: truths: - "Installed tab for Hermes agents shows two labelled sections: Managed and Native" - "Native skills display no remove/update/rollback buttons" - "Managed skills on Hermes agents display full writable actions (install, update, remove)" - "Install dialog sends agentId in body, not agentSkillsDir" - "Uninstall sends agentId as query param, not in body" artifacts: - path: "ui/src/api/skillRegistry.ts" provides: "Updated API types and calls using agentId instead of agentSkillsDir" contains: "agentId" - path: "ui/src/pages/SkillBrowser.tsx" provides: "Dual-section Installed tab with Managed/Native labels for Hermes agents" contains: "managedSkills" - path: "ui/src/components/SkillCard.tsx" provides: "isReadOnly prop that hides action buttons for native skills" contains: "isReadOnly" key_links: - from: "ui/src/pages/SkillBrowser.tsx" to: "ui/src/api/skillRegistry.ts" via: "listAgentSkills returns AgentSkillEntry[] with source field" pattern: "listAgentSkills" - from: "ui/src/pages/SkillBrowser.tsx" to: "ui/src/components/SkillCard.tsx" via: "isReadOnly prop passed for native skills" pattern: "isReadOnly.*native" --- Update the Skill Browser UI to show managed vs native sections for Hermes agents, make native skills read-only, and switch all API calls from agentSkillsDir to agentId. Purpose: Users managing Hermes agents need to clearly distinguish Nexus-managed skills (full control) from built-in native skills (view/rate only). The UI must also stop sending filesystem paths to the server. Output: Updated API client, dual-section Installed tab, read-only SkillCard variant. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/19-adapter-aware-install-uninstall/19-RESEARCH.md @.planning/phases/19-adapter-aware-install-uninstall/19-01-SUMMARY.md Read these source files before modifying: - ui/src/api/skillRegistry.ts (or similar — the API client for skill registry) - ui/src/pages/SkillBrowser.tsx - ui/src/components/SkillCard.tsx (if exists — may be inline in SkillBrowser) GET /skill-registry/agents/:agentId/skills Response: Array<{ skillId: string; source: "managed" | "native"; installedAt: number }> POST /skill-registry/skills/:sourceId/:slug/install Body: { agentId: string } (was: { agentSkillsDir: string }) DELETE /skill-registry/skills/:sourceId/:slug?agentId=xxx Query: agentId (was: body.agentSkillsDir) POST /skill-registry/skills/:sourceId/:slug/rollback Body: { versionId: string, agentId: string } (was: { versionId, agentSkillsDir }) POST /skill-registry/agents/:agentId/groups Body: { groupId: string } (was: { groupId, agentSkillsDir? }) DELETE /skill-registry/agents/:agentId/groups/:groupId (no body needed — agentId in URL) Task 1: Update API client types and calls ui/src/api/skillRegistry.ts ui/src/api/skillRegistry.ts (or search for skill registry API functions — may be in a different file like ui/src/api/skillGroups.ts or similar) 1. Add the `AgentSkillEntry` type: ```typescript export type AgentSkillEntry = { skillId: string; source: "managed" | "native"; installedAt: number; }; ``` 2. Update `listAgentSkills` return type from `string[]` to `AgentSkillEntry[]`. 3. Update `installSkill` (or equivalent) to send `{ agentId }` in the POST body instead of `{ agentSkillsDir }`. 4. Update `uninstallSkill` (or equivalent) to pass `agentId` as a query parameter on the DELETE request instead of in the body. 5. Update `rollbackSkill` (or equivalent) to send `{ versionId, agentId }` instead of `{ versionId, agentSkillsDir }`. 6. Update group assign/remove calls to NOT send `agentSkillsDir` in the body (agentId is already in the URL). 7. Remove any `agentSkillsDir` parameter from all exported API functions. Replace with `agentId: string` where needed. cd nexus && pnpm tsc --noEmit --project ui/tsconfig.json 2>&1 | head -30 - AgentSkillEntry type exported with skillId, source, installedAt fields - listAgentSkills returns AgentSkillEntry[] not string[] - installSkill sends agentId in body, not agentSkillsDir - uninstallSkill sends agentId as query param - rollbackSkill sends agentId in body, not agentSkillsDir - No references to agentSkillsDir in any API function - TypeScript compiles without errors API client uses agentId for all skill operations, returns typed AgentSkillEntry objects Task 2: Dual-section Installed tab and read-only SkillCard ui/src/pages/SkillBrowser.tsx, ui/src/components/SkillCard.tsx ui/src/pages/SkillBrowser.tsx, ui/src/components/SkillCard.tsx (if exists) **SkillCard.tsx** (or inline skill card component): 1. Add `isReadOnly?: boolean` and `source?: "managed" | "native"` props to the component's props interface. 2. When `isReadOnly` is true: - Hide the Remove/Uninstall button - Hide the Update button - Hide the Rollback button - Show a small "Native" badge (e.g., a gray `` from the UI library or a Tailwind-styled span) - Rating/view actions remain visible 3. When `source === "managed"` and skill is installed: - Show all action buttons as before (this is the default behavior, no change needed) **SkillBrowser.tsx** — Installed tab changes: 1. **Remove agentSkillsDir state and input.** The install dialog currently has a text input for `agentSkillsDir`. Remove it entirely. The dialog already shows agent selection buttons with `agent.id` — use that directly. 2. **Update install dialog** to pass `agentId` to the API call instead of `agentSkillsDir`. 3. **Update uninstall handler** to pass `agentId` to the API call instead of `agentSkillsDir`. 4. **Per-agent skill query on Installed tab:** ```typescript const { data: agentInstalledSkills = [] } = useQuery({ queryKey: ["agentInstalledSkills", selectedAgentId], queryFn: () => skillRegistryApi.listAgentSkills(selectedAgentId), enabled: tab === "installed" && !!selectedAgentId, }); ``` 5. **Split skills into managed/native sections:** ```typescript const managedSkills = agentInstalledSkills.filter((s) => s.source === "managed"); const nativeSkills = agentInstalledSkills.filter((s) => s.source === "native"); ``` 6. **Render two labelled sections** when viewing a Hermes agent's installed skills: - "Managed" section heading — renders `managedSkills` with full SkillCard actions - "Native" section heading — renders `nativeSkills` with `isReadOnly={true}` and `source="native"` - Use simple heading elements or dividers: ```tsx {managedSkills.length > 0 && ( <>

Managed

{managedSkills.map(skill => )} )} {nativeSkills.length > 0 && ( <>

Native

{nativeSkills.map(skill => )} )} ``` - For non-Hermes agents (where all skills are managed), render a single list without section headings — the experience is unchanged. 7. **Conditional sections:** Only show the Managed/Native split when `nativeSkills.length > 0`. For non-Hermes agents this will always be 0, so no UI change for them.
cd nexus && pnpm tsc --noEmit --project ui/tsconfig.json 2>&1 | head -30 - SkillCard accepts isReadOnly and source props - isReadOnly=true hides remove/update/rollback buttons - source="native" shows Native badge - SkillBrowser Installed tab splits into Managed/Native sections when native skills exist - Install dialog sends agentId not agentSkillsDir - No agentSkillsDir text input in the dialog - Uninstall handler sends agentId as query param - Non-Hermes agents see unchanged single-list UI - TypeScript compiles without errors Hermes agents show Managed/Native split in Installed tab, native skills are read-only, all API calls use agentId
- TypeScript compiles: `cd nexus && pnpm tsc --noEmit --project ui/tsconfig.json` - No agentSkillsDir references: grep for `agentSkillsDir` in ui/src/ should return 0 matches - isReadOnly prop exists: grep for `isReadOnly` in SkillCard component - Managed/Native split: grep for `managedSkills` and `nativeSkills` in SkillBrowser - Hermes agents show two labelled sections: Managed (full actions) and Native (read-only + badge) - Non-Hermes agents see unchanged UI (no section headings) - All API calls use agentId instead of agentSkillsDir - Install dialog no longer has a text input for skill directory path - TypeScript compiles clean After completion, create `.planning/phases/19-adapter-aware-install-uninstall/19-03-SUMMARY.md`