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>
242 lines
10 KiB
Markdown
242 lines
10 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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)
|
|
|
|
<interfaces>
|
|
<!-- From Plan 01 (updated API response shape) -->
|
|
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)
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Update API client types and calls</name>
|
|
<files>
|
|
ui/src/api/skillRegistry.ts
|
|
</files>
|
|
<read_first>
|
|
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)
|
|
</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd nexus && pnpm tsc --noEmit --project ui/tsconfig.json 2>&1 | head -30</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>API client uses agentId for all skill operations, returns typed AgentSkillEntry objects</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Dual-section Installed tab and read-only SkillCard</name>
|
|
<files>
|
|
ui/src/pages/SkillBrowser.tsx,
|
|
ui/src/components/SkillCard.tsx
|
|
</files>
|
|
<read_first>
|
|
ui/src/pages/SkillBrowser.tsx,
|
|
ui/src/components/SkillCard.tsx (if exists)
|
|
</read_first>
|
|
<action>
|
|
**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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd nexus && pnpm tsc --noEmit --project ui/tsconfig.json 2>&1 | head -30</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>Hermes agents show Managed/Native split in Installed tab, native skills are read-only, all API calls use agentId</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- 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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/19-adapter-aware-install-uninstall/19-03-SUMMARY.md`
|
|
</output>
|