nexus/.planning/milestones/v1.2.1-phases/19-adapter-aware-install-uninstall/19-03-PLAN.md
Mikkel Georgsen 3c85784b6d [nexus] chore: migrate .planning/ from agent repo to nexus repo
Planning artifacts (milestones v1.0-v1.2.1, v1.3 queue, PROJECT.md,
STATE.md, config) now live alongside the code they describe.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:08:50 +00:00

10 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
19-adapter-aware-install-uninstall 03 execute 2
19-01
ui/src/api/skillRegistry.ts
ui/src/pages/SkillBrowser.tsx
ui/src/components/SkillCard.tsx
true
HERM-01
HERM-02
HERM-03
truths artifacts key_links
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
path provides contains
ui/src/api/skillRegistry.ts Updated API types and calls using agentId instead of agentSkillsDir agentId
path provides contains
ui/src/pages/SkillBrowser.tsx Dual-section Installed tab with Managed/Native labels for Hermes agents managedSkills
path provides contains
ui/src/components/SkillCard.tsx isReadOnly prop that hides action buttons for native skills isReadOnly
from to via pattern
ui/src/pages/SkillBrowser.tsx ui/src/api/skillRegistry.ts listAgentSkills returns AgentSkillEntry[] with source field listAgentSkills
from to via pattern
ui/src/pages/SkillBrowser.tsx ui/src/components/SkillCard.tsx isReadOnly prop passed for native skills 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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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 `<Badge>` 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 && (
       <>
         <h3 className="text-sm font-medium text-muted-foreground mt-4 mb-2">Managed</h3>
         {managedSkills.map(skill => <SkillCard ... />)}
       </>
     )}
     {nativeSkills.length > 0 && (
       <>
         <h3 className="text-sm font-medium text-muted-foreground mt-4 mb-2">Native</h3>
         {nativeSkills.map(skill => <SkillCard ... isReadOnly={true} source="native" />)}
       </>
     )}
     ```
   - 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

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/19-adapter-aware-install-uninstall/19-03-SUMMARY.md`