11 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 28-ollama-integration | 02 | execute | 2 |
|
|
true |
|
|
Purpose: Users can discover, browse, and select local Ollama models when configuring a Hermes agent, with hardware-aware recommendations highlighted. When Ollama is absent, users see install instructions. Hermes native skills are clearly labeled in the Skills tab. Output: Working model selector dropdown, install callout, and Hermes skill badges.
<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/28-ollama-integration/28-RESEARCH.md @.planning/phases/28-ollama-integration/28-01-SUMMARY.md@ui/src/adapters/hermes-local/config-fields.tsx @ui/src/adapters/types.ts @ui/src/api/client.ts @ui/src/pages/AgentDetail.tsx (AgentSkillsTab around line 2362, unmanagedSkillRows around 2566)
GET /api/companies/:companyId/ollama/status Response: { installed: boolean; version: string | null; installUrl: string }
GET /api/companies/:companyId/ollama/models Response: { models: OllamaModel[]; ramGb: number } Where OllamaModel = { name: string; // e.g. "qwen2.5-coder:32b" parameterSize: string; // e.g. "32.8B" quantization: string; // e.g. "Q4_K_M" sizeBytes: number; family: string; // e.g. "qwen2" recommended: boolean; recommendationReason: string | null; }
AdapterConfigFieldsProps: { mode, isCreate, adapterType, values, set, config, eff, mark, models, hideInstructionsFile }
Task 1: Create ollamaApi client and enhance HermesLocalConfigFields with model dropdown ui/src/api/ollama.ts, ui/src/adapters/hermes-local/config-fields.tsx - ui/src/api/client.ts (full file — for api.get pattern) - ui/src/api/health.ts (for simple API client pattern) - ui/src/adapters/hermes-local/config-fields.tsx (full file — current state) - ui/src/adapters/types.ts (AdapterConfigFieldsProps interface) - .planning/phases/28-ollama-integration/28-RESEARCH.md (Pattern 3, Pattern 4, Pitfall 1, Pitfall 4) 1. Create `ui/src/api/ollama.ts`: - Import `api` from "./client" (the request helper) - Define types: `OllamaStatus { installed: boolean; version: string | null; installUrl: string }`, `OllamaModel { name: string; parameterSize: string; quantization: string; sizeBytes: number; family: string; recommended: boolean; recommendationReason: string | null }`, `OllamaModelsResponse { models: OllamaModel[]; ramGb: number }` - Export `ollamaApi` object with: - `status(companyId: string): Promise` — GET `/companies/${companyId}/ollama/status` - `models(companyId: string): Promise` — GET `/companies/${companyId}/ollama/models`2. Rewrite `ui/src/adapters/hermes-local/config-fields.tsx`:
- Add imports: `useQuery` from `@tanstack/react-query`, `ollamaApi` from `../../api/ollama`
- Need companyId: extract from URL params using `useParams` from react-router-dom, or accept via a context. Check existing config-fields patterns for how companyId is obtained — look at the component's parent to find how it gets companyId. If not available via props, use `useParams<{ companyId?: string }>()` matching the route pattern `/companies/:companyId/agents/...`.
- Add two queries (only enabled when companyId is truthy):
```
const { data: ollamaStatus } = useQuery({ queryKey: ["ollama", "status", companyId], queryFn: () => ollamaApi.status(companyId!), enabled: Boolean(companyId), staleTime: 60_000 })
const { data: ollamaModels } = useQuery({ queryKey: ["ollama", "models", companyId], queryFn: () => ollamaApi.models(companyId!), enabled: Boolean(companyId && ollamaStatus?.installed), staleTime: 60_000 })
```
- Replace the current free-text Model `<DraftInput>` with a hybrid control:
- **When ollamaStatus?.installed AND ollamaModels?.models.length > 0**: Render a `<select>` dropdown with an empty option ("Select a model..."), then each ollamaModels.models item as an `<option>`. Recommended models get a star prefix in label (e.g., "* qwen2.5-coder:7b (7B, Q4_K_M) - Recommended for your system"). Non-recommended models show plain label (e.g., "qwen2.5-coder:32b (32.8B, Q4_K_M)"). Add a final option "Other (manual entry)..." that switches to a text input.
- **When ollamaStatus?.installed === false**: Render an install callout div: "Ollama is not detected." with a link to ollamaStatus.installUrl ("Install Ollama"). Below the callout, keep the existing DraftInput for manual model entry (user may have a remote provider).
- **When ollamaStatus is loading or undefined**: Show the existing DraftInput as fallback.
- CRITICAL (Pitfall 1 + Pitfall 4): When an Ollama model is selected from the dropdown, set ALL THREE fields atomically:
- Create mode: `set!({ model: selectedModel, provider: "custom", base_url: "http://localhost:11434/v1" })`
- Edit mode: `mark("adapterConfig", "model", selectedModel)`, `mark("adapterConfig", "provider", "custom")`, `mark("adapterConfig", "base_url", "http://localhost:11434/v1")`
- When "Other (manual entry)" is selected or when using the fallback text input, do NOT set provider/base_url — user manages those fields themselves.
- Style the select with the existing `inputClass` CSS classes for consistency.
- Style the install callout: use `rounded-md border border-amber-500/30 bg-amber-500/5 p-3 text-sm` with an external link icon or similar.
cd /opt/nexus/ui && npx tsc --noEmit 2>&1 | head -20
- grep -q "ollamaApi" ui/src/api/ollama.ts
- grep -q "status" ui/src/api/ollama.ts
- grep -q "models" ui/src/api/ollama.ts
- grep -q "ollamaApi" ui/src/adapters/hermes-local/config-fields.tsx
- grep -q "useQuery" ui/src/adapters/hermes-local/config-fields.tsx
- grep -q 'provider.*custom' ui/src/adapters/hermes-local/config-fields.tsx
- grep -q 'base_url.*localhost:11434' ui/src/adapters/hermes-local/config-fields.tsx
- grep -q "ollama.com/download" ui/src/adapters/hermes-local/config-fields.tsx
- grep -q "recommended" ui/src/adapters/hermes-local/config-fields.tsx
Hermes config shows Ollama model dropdown when Ollama is detected, install callout when absent, and manual fallback. Selecting an Ollama model sets provider:custom + base_url atomically. TypeScript compiles without errors.
Task 2: Add Hermes skill badge rendering in AgentSkillsTab
ui/src/pages/AgentDetail.tsx
- ui/src/pages/AgentDetail.tsx (lines 2550-2600 for unmanagedSkillRows, lines 2950-2970 for originLabel rendering, lines 3050-3070 for unmanagedSkillRows section header)
Update the `AgentSkillsTab` component in `AgentDetail.tsx` to better distinguish Hermes native skills:
1. In the unmanagedSkillRows section header (around line 3063), change the label text to be conditional:
- If `agent.adapterType === "hermes_local"`: display `(${unmanagedSkillRows.length}) Hermes native skills & user-installed skills`
- Otherwise: keep existing text `(${unmanagedSkillRows.length}) User-installed skills, not managed by ${VOCAB.appName}`
2. In the `renderSkillRow` function (around line 2960-2961), where `skill.readOnly && skill.originLabel` renders a `<p>` with originLabel text:
- When `skill.originLabel === "Hermes skill"`, render a small inline badge instead of plain text:
`<span className="inline-flex items-center rounded-full bg-purple-500/10 px-2 py-0.5 text-xs font-medium text-purple-400">Hermes skill</span>`
- Keep the existing `<p>` rendering for other originLabel values unchanged.
3. The `unmanagedSkillRows` data already flows from the skill registry API which includes Hermes native skills with `originLabel: "Hermes skill"` and `readOnly: true`. No data changes needed.
cd /opt/nexus/ui && npx tsc --noEmit 2>&1 | head -20
- grep -q "Hermes skill" ui/src/pages/AgentDetail.tsx
- grep -q "hermes_local" ui/src/pages/AgentDetail.tsx
- grep -q "Hermes native" ui/src/pages/AgentDetail.tsx
- grep -q "purple" ui/src/pages/AgentDetail.tsx
Hermes native skills show a purple "Hermes skill" badge in the Skills tab. Section header indicates "Hermes native skills" when viewing a Hermes agent. TypeScript compiles without errors.
- `cd /opt/nexus/ui && npx tsc --noEmit` — no type errors
- Config-fields dropdown renders when Ollama is available (verified by code structure)
- Install callout renders when Ollama is absent (verified by code structure)
- Hermes skill badge uses distinct purple styling
<success_criteria>
- ollamaApi client exists with status() and models() methods
- HermesLocalConfigFields shows model dropdown when Ollama detected, install callout when absent
- Selecting an Ollama model atomically sets model + provider:custom + base_url
- Manual model entry works as fallback
- Hermes native skills show "Hermes skill" badge in Skills tab
- All TypeScript compiles without errors </success_criteria>