diff --git a/.planning/MILESTONES.md b/.planning/MILESTONES.md index fdc68e18..3d8880c1 100644 --- a/.planning/MILESTONES.md +++ b/.planning/MILESTONES.md @@ -1,5 +1,27 @@ # Milestones +## v1.5 Smart Onboarding + Personal AI Assistant (Shipped: 2026-04-03) + +**Phases completed:** 6 phases, 13 plans, 19 tasks + +**Key accomplishments:** + +- Hardware tier detection (Apple Silicon/GPU/CPU-only) via systeminformation with 3s timeout, file-backed mode persistence via Zod-validated nexus-settings service, extended model catalog with tier arrays, and unauthenticated /api/system/providers endpoint +- One-liner: +- `server/src/services/puter-proxy.ts` +- One-liner: +- 4-step onboarding wizard with Puter/Google/API-key provider cards, adapter auto-detection badges, and post-company-creation credential storage +- Human verification checkpoint for complete provider selection onboarding flow — auto-approved under auto_advance mode, deferred to UAT +- 5-step onboarding wizard with skip buttons on steps 1/2/4, summary screen as step 5, and "Start chatting" CTA that creates workspace then opens chat panel. +- File-backed assistant memory service with write-time credential sanitization and REST endpoints mounted in app.ts. +- One-liner: +- Real AI streaming via puterProxyService with memory-injected system prompt, SSE format fix, and assistant-to-PM handoff route with wired UI button. +- chatFileRoutes and nexusSettingsRoutes mounted in app.ts; voiceEnabled added to nexus-settings; usePiperTts hook and TtsButton component created with @mintplex-labs/piper-tts-web WASM synthesis +- VoiceStep onboarding component (mic detection, enable/skip) inserted as wizard step 4; VoiceRecordButton (STT) and TtsButton (TTS) wired into PersonalAssistant for full voice I/O +- One-liner: + +--- + ## v1.4 Hermes Default Provider (Shipped: 2026-04-02) **Phases completed:** 3 phases, 6 plans, 9 tasks diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index ec2735ca..306ef262 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -159,4 +159,4 @@ After every `/gsd:complete-milestone`, perform an upstream rebase before startin - Every step skippable, local-first framed as privacy premium --- -*Last updated: 2026-04-02 after v1.5 milestone started* +*Last updated: 2026-04-03 after v1.5 milestone* diff --git a/.planning/STATE.md b/.planning/STATE.md index f7c40723..0ef24bf3 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,7 +4,7 @@ milestone: v1.5 milestone_name: Smart Onboarding + Personal AI Assistant status: verifying stopped_at: Completed 35-npx-buildthis-cli/35-01 -last_updated: "2026-04-03T23:03:19.182Z" +last_updated: "2026-04-03T23:03:36.034Z" last_activity: 2026-04-03 progress: total_phases: 6 diff --git a/.planning/milestones/v1.5-REQUIREMENTS.md b/.planning/milestones/v1.5-REQUIREMENTS.md new file mode 100644 index 00000000..2c452fca --- /dev/null +++ b/.planning/milestones/v1.5-REQUIREMENTS.md @@ -0,0 +1,110 @@ +# Requirements Archive: v1.5 Smart Onboarding + Personal AI Assistant + +**Archived:** 2026-04-03 +**Status:** SHIPPED + +For current requirements, see `.planning/REQUIREMENTS.md`. + +--- + +# Requirements: Nexus v1.5 — Smart Onboarding + Personal AI Assistant + +**Defined:** 2026-04-02 +**Core Value:** A fresh onboard asks for ONE thing (root directory), auto-creates PM + Engineer agents, and drops you in the dashboard. + +## v1.5 Requirements + +### Onboarding + +- [x] **ONBD-01**: User can select mode (Personal AI Assistant / Project Builder / Both) during onboarding +- [x] **ONBD-02**: System auto-detects GPU, RAM, and Apple Silicon unified memory within 5 seconds +- [x] **ONBD-03**: System recommends best local model from pre-built JSON database based on detected hardware +- [x] **ONBD-04**: User can skip any onboarding step without blocking subsequent steps +- [x] **ONBD-05**: User sees summary screen showing configured providers and agent-model pairings +- [x] **ONBD-06**: User can go from summary screen directly into chat with one click +- [x] **ONBD-07**: Local AI framed as privacy premium ("runs entirely on your machine, no accounts, works offline") + +### Cloud Providers + +- [x] **CLOUD-01**: User gets working AI via Puter.js with zero API keys and no sign-up required +- [x] **CLOUD-02**: Puter.js integrated as server-proxied adapter (not browser-direct) with full cost tracking +- [x] **CLOUD-03**: User can sign in via Google OAuth to access Gemini free tier +- [x] **CLOUD-04**: System auto-detects installed tools (Hermes, Claude Code, OpenClaw) and pre-fills configuration +- [x] **CLOUD-05**: User can enter API keys for subscription providers during onboarding + +### Voice + +- [x] **VOICE-01**: User gets Piper TTS speech output that works on CPU-only hardware +- [x] **VOICE-02**: Piper TTS pre-warms on first use with visible download progress (no silent 15-30s hang) +- [x] **VOICE-03**: Voice features (Whisper STT + Piper TTS) offered during onboarding based on hardware capability + +### Personal AI Assistant + +- [x] **ASST-01**: User has persistent memory across chat sessions (summary-based, injected into system prompts) +- [x] **ASST-02**: Memory content sanitized at write time to prevent prompt injection +- [x] **ASST-03**: User can hand off an assistant conversation to a PM agent with one click, transferring context +- [x] **ASST-04**: Assistant and Project Builder modes work standalone or together + +### CLI + +- [x] **CLI-01**: User can run `npx buildthis` to bootstrap Nexus from scratch +- [x] **CLI-02**: CLI bootstrapper detects hardware and walks through the same provider tiering as web onboarding + +## Future Requirements + +### Cloud + +- **CLOUD-F01**: OpenAI OAuth free tier (unstable API, defer to v2+) + +### Assistant + +- **ASST-F01**: MCP connections for assistant mode + +### Voice + +- **VOICE-F01**: Server-side TTS fallback for headless mode + +## Out of Scope + +| Feature | Reason | +|---------|--------| +| OpenAI OAuth | Endpoint specifics unstable, low confidence on free tier details | +| MCP tool connections | Complexity too high for v1.5; assistant works without it | +| Server-side Piper TTS | Browser WASM sufficient; headless is edge case | +| DB schema changes | Upstream sync constraint — all state in existing JSONB/files | +| Vector database for memory | Summary-based approach sufficient; no infra overhead | + +## Traceability + +| Requirement | Phase | Status | +|-------------|-------|--------| +| ONBD-01 | Phase 30 | Complete | +| ONBD-02 | Phase 30 | Complete | +| ONBD-03 | Phase 30 | Complete | +| ONBD-07 | Phase 30 | Complete | +| CLOUD-01 | Phase 31 | Complete | +| CLOUD-02 | Phase 31 | Complete | +| CLOUD-03 | Phase 31 | Complete | +| CLOUD-04 | Phase 31 | Complete | +| CLOUD-05 | Phase 31 | Complete | +| ONBD-04 | Phase 32 | Complete | +| ONBD-05 | Phase 32 | Complete | +| ONBD-06 | Phase 32 | Complete | +| ASST-01 | Phase 33 | Complete | +| ASST-02 | Phase 33 | Complete | +| ASST-03 | Phase 33 | Complete | +| ASST-04 | Phase 33 | Complete | +| VOICE-01 | Phase 34 | Complete | +| VOICE-02 | Phase 34 | Complete | +| VOICE-03 | Phase 34 | Complete | +| CLI-01 | Phase 35 | Complete | +| CLI-02 | Phase 35 | Complete | + +**Coverage:** +- v1.5 requirements: 21 total +- Mapped to phases: 21 +- Unmapped: 0 ✓ + +--- +*Requirements defined: 2026-04-02* +*Last updated: 2026-04-02 after roadmap created (phases 30-35)* diff --git a/.planning/milestones/v1.5-ROADMAP.md b/.planning/milestones/v1.5-ROADMAP.md new file mode 100644 index 00000000..1c32e6c0 --- /dev/null +++ b/.planning/milestones/v1.5-ROADMAP.md @@ -0,0 +1,245 @@ +# Roadmap: Nexus + +## Milestones + +- ✅ **v1.2.1 Universal Skill Management** - Phase 1 (shipped 2026-04-01) +- ✅ **v1.3 Chat & PWA** - Phases 21-26 (shipped 2026-04-02) +- ✅ **v1.4 Hermes Default Provider** - Phases 27-29 (shipped 2026-04-02) +- 🚧 **v1.5 Smart Onboarding + Personal AI Assistant** - Phases 30-35 (in progress) + +--- + +
+✅ v1.2.1 Universal Skill Management (Phase 1) - SHIPPED 2026-04-01 + +### Phase 1: Foundation +**Goal**: Establish the display-layer rename infrastructure, git hygiene tooling, and rebase safety primitives that all subsequent phases depend on +**Plans**: 2/2 plans complete + +Plans: +- [x] 01-01-PLAN.md — Branding package, VOCAB constants, commit-msg hook +- [x] 01-02-PLAN.md — Zone taxonomy, rerere config, rebase safety infrastructure + +
+ +
+✅ v1.3 Chat & PWA (Phases 21-26) - SHIPPED 2026-04-02 + +### Phase 21: Chat Foundation +**Goal**: Users can have real-time chat conversations with agents +**Plans**: 7/7 plans complete + +### Phase 22: Agent Streaming +**Goal**: Agent responses stream in real-time with identity, edit, retry, and stop controls +**Plans**: 5/5 plans complete + +### Phase 23: Brainstormer Flow +**Goal**: Users can turn a chat conversation into a tracked project with one handoff action +**Plans**: 4/4 plans complete + +### Phase 24: Search, History & Branching +**Goal**: Users can find, bookmark, branch, and export any conversation +**Plans**: 4/4 plans complete + +### Phase 25: File System +**Goal**: Users can upload, preview, and version files within chat; voice input transcribes speech to text +**Plans**: 9/9 plans complete + +### Phase 26: PWA & Performance +**Goal**: Nexus installs as a PWA, works offline, and loads fast on mobile +**Plans**: 5/5 plans complete + +
+ +
+✅ v1.4 Hermes Default Provider (Phases 27-29) - SHIPPED 2026-04-02 + +### Phase 27: Hermes Adapter +**Goal**: Users can create a Hermes agent in Nexus, configure it, and have it execute heartbeats that spawn `hermes chat -q`, return a result, and persist the session across runs +**Plans**: 1/1 plans complete + +Plans: +- [x] 27-01-PLAN.md — Close four integration gaps: SESSIONED_LOCAL_ADAPTERS, create-mode toolsets bug, duplicate constant, session codec test + +### Phase 28: Ollama Integration & Agent Surface +**Goal**: Users can see which Ollama models are available, get a recommendation for their hardware, configure any Hermes agent to use a local model, and see Hermes-specific runtime data in the dashboard and agent config +**Plans**: 3/3 plans complete + +Plans: +- [x] 28-01-PLAN.md — Ollama service, routes, model catalog, and unit tests +- [x] 28-02-PLAN.md — UI model selector dropdown, install callout, Hermes skill badge +- [x] 28-03-PLAN.md — Hermes stateJson runtime data and dashboard HermesRuntimeCard + +### Phase 29: Default Provider & End-to-End +**Goal**: A fresh Nexus install with only Hermes and Ollama works end-to-end — onboarding offers Hermes as the default, PM and Engineer templates run correctly on the Hermes runtime, and GSD workflow tasks complete successfully +**Plans**: 2/2 plans complete + +Plans: +- [x] 29-01-PLAN.md — Adapter probe route, onboarding wizard Hermes fallback, adapter-neutral templates +- [x] 29-02-PLAN.md — Hermes skill injection via promptTemplate, integration tests + +
+ +--- + +### 🚧 v1.5 Smart Onboarding + Personal AI Assistant (In Progress) + +**Milestone Goal:** The definitive onboarding experience — hardware detection, tiered provider setup (local/free cloud/paid), and a Personal AI Assistant mode that coexists with the Project Builder. + +## Phases + +- [x] **Phase 30: Hardware Detection + Mode Selection** — Unauthenticated hardware probe, Apple Silicon unified memory handling, model recommendation database, and mode selector that gates all assistant-specific features (completed 2026-04-02) +- [x] **Phase 31: Puter.js Zero-Config Cloud** — Server-proxied Puter.js adapter with full cost tracking, Google OAuth PKCE tier, and subscription auto-detection; no API keys required for zero-config path (completed 2026-04-03) +- [x] **Phase 32: Multi-Step Onboarding Wizard** — Assemble all provider tiers and hardware data into a skippable multi-step wizard; summary screen routes directly into chat (completed 2026-04-03) +- [x] **Phase 33: Persistent Memory + Personal Assistant Mode** — File-backed memory with write-time sanitization, PersonalAssistantPage, conversation handoff to PM agent (completed 2026-04-03) +- [x] **Phase 34: Voice** — Piper TTS with pre-warm progress, Whisper STT wired into voice service, onboarding voice step activated (completed 2026-04-03) +- [x] **Phase 35: npx buildthis CLI** — Standalone bootstrapper package with hardware detection and provider tiering parity with web onboarding (completed 2026-04-03) + +--- + +## Phase Details + +### Phase 30: Hardware Detection + Mode Selection +**Goal**: Users see accurate hardware information during onboarding, get a model recommendation matched to their machine, and choose a mode that correctly gates all downstream features — with the probe working before board auth exists +**Depends on**: Phase 29 (v1.4 shipped) +**Requirements**: ONBD-01, ONBD-02, ONBD-03, ONBD-07 +**Success Criteria** (what must be TRUE): + 1. On a fresh install (before any board auth token exists), the hardware probe returns GPU, RAM, and Apple Silicon unified memory data within 5 seconds + 2. A Mac Mini M4 reports "unified memory" (not VRAM) with the 0.75 multiplier applied and copy that says "runs entirely on your machine" + 3. The mode selector (Personal AI Assistant / Project Builder / Both) is visible during onboarding and the selected mode is persisted; assistant-specific UI is hidden when Project Builder-only is chosen + 4. The model recommendation shown to the user matches an entry in the pre-built JSON catalog for the detected hardware tier (GPU / Apple Silicon / CPU-only) +**Plans**: 2 plans + +Plans: +- [x] 30-01-PLAN.md — Hardware service, nexus-settings service, model catalog extension, routes, and tests +- [x] 30-02-PLAN.md — ModeSelector, HardwareSummaryStep, useHardwareInfo hook, multi-step wizard wiring + +### Phase 31: Puter.js Zero-Config Cloud +**Goal**: Users without Ollama installed can reach working AI in one click via Puter.js — all calls server-proxied, tokens server-stored, cost tracked; Google OAuth and subscription auto-detection round out the provider tier +**Depends on**: Phase 30 +**Requirements**: CLOUD-01, CLOUD-02, CLOUD-03, CLOUD-04, CLOUD-05 +**Success Criteria** (what must be TRUE): + 1. A user with no Ollama and no API keys clicks "Continue with Puter" in onboarding, completes the Puter auth popup, and immediately gets a working chat response — no API key entry required + 2. All Puter AI calls flow through `POST /api/puter-proxy/chat` (verifiable in server logs); the Puter auth token is stored server-side via secretService, not in localStorage + 3. Token cost for Puter responses appears in the cost tracking view, attributed correctly per conversation + 4. A user with Hermes, Claude Code, or OpenClaw already installed sees those tools pre-filled in the provider configuration step with no manual entry + 5. A user clicking "Sign in with Google" for Gemini completes PKCE OAuth and gets a Gemini-backed chat response; the UI displays a policy-risk note that Google OAuth may trigger abuse detection +**Plans**: 4 plans + +Plans: +- [x] 31-01-PLAN.md — Puter proxy service, routes, unit tests, and app.ts wiring +- [x] 31-02-PLAN.md — Google OAuth PKCE service, routes, API key storage route +- [x] 31-03-PLAN.md — Provider Selection UI step, PuterAuthButton, GoogleOAuthButton, ApiKeyEntryForm, 4-step wizard wiring +- [x] 31-04-PLAN.md — Google OAuth claim endpoint, human verification of full onboarding flow +**UI hint**: yes + +### Phase 32: Multi-Step Onboarding Wizard +**Goal**: Users move through a complete, skippable onboarding flow that assembles hardware data, provider selection, and voice options into a summary screen — and can jump straight into chat from there +**Depends on**: Phase 31 +**Requirements**: ONBD-04, ONBD-05, ONBD-06 +**Success Criteria** (what must be TRUE): + 1. A user can click "Skip" on every onboarding step (hardware, provider, voice) and reach the summary screen; the resulting workspace has at least one working agent with a valid provider + 2. The summary screen shows the configured providers and agent-model pairings for the selected mode; no corporate language ("company", "CEO", "mission") appears anywhere in the flow + 3. From the summary screen, one click navigates directly to the Personal Assistant chat or the project dashboard (depending on chosen mode) with no additional prompts +**Plans**: 1 plan + +Plans: +- [x] 32-01-PLAN.md — Summary step, skip buttons, chat handoff +**UI hint**: yes + +### Phase 33: Persistent Memory + Personal Assistant Mode +**Goal**: Users in Personal AI Assistant mode accumulate memory across sessions that shapes future responses — with no risk of credentials leaking into prompts — and can hand off any conversation to a PM agent with context intact +**Depends on**: Phase 32 +**Requirements**: ASST-01, ASST-02, ASST-03, ASST-04 +**Success Criteria** (what must be TRUE): + 1. A fact stated in one chat session ("I prefer TypeScript") is referenced correctly by the assistant in a new session started after closing and reopening the browser + 2. Pasting an API key or token into chat and then starting a new session results in the assistant having no knowledge of that credential — the sanitization blocklist prevented it from being stored + 3. A user clicks "Turn this into a project" in an assistant conversation; a PM agent is created with a system message containing the conversation summary and they land on the project dashboard + 4. A user with mode set to "Both" can switch between Personal Assistant chat and the project dashboard without losing context or cross-contaminating assistant memory with project agent messages +**Plans**: 3 plans + +Plans: +- [x] 33-01-PLAN.md — Memory sanitizer, assistant memory service, REST routes, and unit tests +- [x] 33-02-PLAN.md — PersonalAssistantPage, useNexusMode hook, sidebar navigation, route wiring +- [x] 33-03-PLAN.md — Real AI streaming with memory injection, assistant-to-PM handoff route and UI +**UI hint**: yes + +### Phase 34: Voice +**Goal**: Users can speak to the assistant (Whisper STT) and hear responses read aloud (Piper TTS) — Piper pre-warms visibly so the first synthesis call does not appear broken, and voice is offered during onboarding based on hardware capability +**Depends on**: Phase 32 +**Requirements**: VOICE-01, VOICE-02, VOICE-03 +**Success Criteria** (what must be TRUE): + 1. On a CPU-only machine (no GPU), enabling Piper TTS in the assistant produces audible speech output within a reasonable time after the first synthesis (not a silent hang) + 2. When Piper's WASM voice model is downloading for the first time, a visible progress indicator is shown before the TTS toggle is enabled; the download completes and TTS works without a page reload + 3. The onboarding voice step offers Whisper STT and Piper TTS toggles only when the hardware detection step has confirmed sufficient capability; on hardware below the threshold, the step is skipped or shows a capability warning +**Plans**: 2 plans + +Plans: +- [x] 34-01-PLAN.md — Fix /transcribe route registration, Piper TTS hook + TtsButton, voiceEnabled in nexus-settings +- [x] 34-02-PLAN.md — VoiceStep onboarding component, wizard step insertion, PersonalAssistant voice wiring +**UI hint**: yes + +### Phase 35: npx buildthis CLI +**Goal**: A developer can run `npx buildthis` on a fresh machine and either open an already-running Nexus or be guided through install — with the same hardware detection and provider tiering as the web onboarding +**Depends on**: Phase 30 (hardware detection service must exist) +**Requirements**: CLI-01, CLI-02 +**Success Criteria** (what must be TRUE): + 1. Running `npx buildthis` on a machine where Nexus is already running opens the Nexus UI in the default browser; running it on a machine with no Nexus guides the user through installation steps + 2. The CLI bootstrapper detects the same hardware tier (GPU / Apple Silicon / CPU-only) as the web onboarding and presents the matching provider tier recommendations in the terminal prompt +**Plans**: 1 plan + +Plans: +- [x] 35-01-PLAN.md — Package scaffold, hardware detection, two-path bootstrap (probe running vs guide install), provider selection, tests + +--- + +## Coverage Validation + +All 21 v1.5 requirements are mapped to exactly one phase. No orphans. + +| Requirement | Phase | +|-------------|-------| +| ONBD-01 | 30 | +| ONBD-02 | 30 | +| ONBD-03 | 30 | +| ONBD-07 | 30 | +| CLOUD-01 | 31 | +| CLOUD-02 | 31 | +| CLOUD-03 | 31 | +| CLOUD-04 | 31 | +| CLOUD-05 | 31 | +| ONBD-04 | 32 | +| ONBD-05 | 32 | +| ONBD-06 | 32 | +| ASST-01 | 33 | +| ASST-02 | 33 | +| ASST-03 | 33 | +| ASST-04 | 33 | +| VOICE-01 | 34 | +| VOICE-02 | 34 | +| VOICE-03 | 34 | +| CLI-01 | 35 | +| CLI-02 | 35 | + +--- + +## Progress + +| Phase | Milestone | Plans Complete | Status | Completed | +|-------|-----------|----------------|--------|-----------| +| 1. Foundation | v1.2.1 | 2/2 | Complete | 2026-04-01 | +| 21. Chat Foundation | v1.3 | 7/7 | Complete | 2026-04-02 | +| 22. Agent Streaming | v1.3 | 5/5 | Complete | 2026-04-02 | +| 23. Brainstormer Flow | v1.3 | 4/4 | Complete | 2026-04-02 | +| 24. Search, History & Branching | v1.3 | 4/4 | Complete | 2026-04-02 | +| 25. File System | v1.3 | 9/9 | Complete | 2026-04-02 | +| 26. PWA & Performance | v1.3 | 5/5 | Complete | 2026-04-02 | +| 27. Hermes Adapter | v1.4 | 1/1 | Complete | 2026-04-02 | +| 28. Ollama Integration & Agent Surface | v1.4 | 3/3 | Complete | 2026-04-02 | +| 29. Default Provider & End-to-End | v1.4 | 2/2 | Complete | 2026-04-02 | +| 30. Hardware Detection + Mode Selection | v1.5 | 2/2 | Complete | 2026-04-03 | +| 31. Puter.js Zero-Config Cloud | v1.5 | 4/4 | Complete | 2026-04-03 | +| 32. Multi-Step Onboarding Wizard | v1.5 | 1/1 | Complete | 2026-04-03 | +| 33. Persistent Memory + Personal Assistant Mode | v1.5 | 3/3 | Complete | 2026-04-03 | +| 34. Voice | v1.5 | 2/2 | Complete | 2026-04-03 | +| 35. npx buildthis CLI | v1.5 | 1/1 | Complete | 2026-04-03 | diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-01-PLAN.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-01-PLAN.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-01-PLAN.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-01-PLAN.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-01-SUMMARY.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-01-SUMMARY.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-01-SUMMARY.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-01-SUMMARY.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-02-PLAN.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-02-PLAN.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-02-PLAN.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-02-PLAN.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-02-SUMMARY.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-02-SUMMARY.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-02-SUMMARY.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-02-SUMMARY.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-CONTEXT.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-CONTEXT.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-CONTEXT.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-CONTEXT.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-RESEARCH.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-RESEARCH.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-RESEARCH.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-RESEARCH.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-UI-SPEC.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-UI-SPEC.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-UI-SPEC.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-UI-SPEC.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-VALIDATION.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-VALIDATION.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-VALIDATION.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-VALIDATION.md diff --git a/.planning/phases/30-hardware-detection-mode-selection/30-VERIFICATION.md b/.planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-VERIFICATION.md similarity index 100% rename from .planning/phases/30-hardware-detection-mode-selection/30-VERIFICATION.md rename to .planning/milestones/v1.5-phases/30-hardware-detection-mode-selection/30-VERIFICATION.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-01-PLAN.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-01-PLAN.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-01-PLAN.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-01-PLAN.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-01-SUMMARY.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-01-SUMMARY.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-01-SUMMARY.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-01-SUMMARY.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-02-PLAN.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-02-PLAN.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-02-PLAN.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-02-PLAN.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-02-SUMMARY.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-02-SUMMARY.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-02-SUMMARY.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-02-SUMMARY.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-03-PLAN.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-03-PLAN.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-03-PLAN.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-03-PLAN.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-03-SUMMARY.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-03-SUMMARY.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-03-SUMMARY.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-03-SUMMARY.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-04-PLAN.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-04-PLAN.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-04-PLAN.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-04-PLAN.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-04-SUMMARY.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-04-SUMMARY.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-04-SUMMARY.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-04-SUMMARY.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-CONTEXT.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-CONTEXT.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-CONTEXT.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-CONTEXT.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-RESEARCH.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-RESEARCH.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-RESEARCH.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-RESEARCH.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-UI-SPEC.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-UI-SPEC.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-UI-SPEC.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-UI-SPEC.md diff --git a/.planning/phases/31-puter.js-zero-config-cloud/31-VALIDATION.md b/.planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-VALIDATION.md similarity index 100% rename from .planning/phases/31-puter.js-zero-config-cloud/31-VALIDATION.md rename to .planning/milestones/v1.5-phases/31-puter.js-zero-config-cloud/31-VALIDATION.md diff --git a/.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md b/.planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-01-PLAN.md similarity index 100% rename from .planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md rename to .planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-01-PLAN.md diff --git a/.planning/phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md b/.planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md similarity index 100% rename from .planning/phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md rename to .planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md diff --git a/.planning/phases/32-multi-step-onboarding-wizard/32-CONTEXT.md b/.planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-CONTEXT.md similarity index 100% rename from .planning/phases/32-multi-step-onboarding-wizard/32-CONTEXT.md rename to .planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-CONTEXT.md diff --git a/.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md b/.planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-RESEARCH.md similarity index 100% rename from .planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md rename to .planning/milestones/v1.5-phases/32-multi-step-onboarding-wizard/32-RESEARCH.md diff --git a/.planning/phases/33-persistent-memory/33-01-PLAN.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-01-PLAN.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-01-PLAN.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-01-PLAN.md diff --git a/.planning/phases/33-persistent-memory/33-01-SUMMARY.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-01-SUMMARY.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-01-SUMMARY.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-01-SUMMARY.md diff --git a/.planning/phases/33-persistent-memory/33-02-PLAN.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-02-PLAN.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-02-PLAN.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-02-PLAN.md diff --git a/.planning/phases/33-persistent-memory/33-02-SUMMARY.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-02-SUMMARY.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-02-SUMMARY.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-02-SUMMARY.md diff --git a/.planning/phases/33-persistent-memory/33-03-PLAN.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-03-PLAN.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-03-PLAN.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-03-PLAN.md diff --git a/.planning/phases/33-persistent-memory/33-03-SUMMARY.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-03-SUMMARY.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-03-SUMMARY.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-03-SUMMARY.md diff --git a/.planning/phases/33-persistent-memory/33-CONTEXT.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-CONTEXT.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-CONTEXT.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-CONTEXT.md diff --git a/.planning/phases/33-persistent-memory/33-RESEARCH.md b/.planning/milestones/v1.5-phases/33-persistent-memory/33-RESEARCH.md similarity index 100% rename from .planning/phases/33-persistent-memory/33-RESEARCH.md rename to .planning/milestones/v1.5-phases/33-persistent-memory/33-RESEARCH.md diff --git a/.planning/phases/34-voice/34-01-PLAN.md b/.planning/milestones/v1.5-phases/34-voice/34-01-PLAN.md similarity index 100% rename from .planning/phases/34-voice/34-01-PLAN.md rename to .planning/milestones/v1.5-phases/34-voice/34-01-PLAN.md diff --git a/.planning/phases/34-voice/34-01-SUMMARY.md b/.planning/milestones/v1.5-phases/34-voice/34-01-SUMMARY.md similarity index 100% rename from .planning/phases/34-voice/34-01-SUMMARY.md rename to .planning/milestones/v1.5-phases/34-voice/34-01-SUMMARY.md diff --git a/.planning/phases/34-voice/34-02-PLAN.md b/.planning/milestones/v1.5-phases/34-voice/34-02-PLAN.md similarity index 100% rename from .planning/phases/34-voice/34-02-PLAN.md rename to .planning/milestones/v1.5-phases/34-voice/34-02-PLAN.md diff --git a/.planning/phases/34-voice/34-02-SUMMARY.md b/.planning/milestones/v1.5-phases/34-voice/34-02-SUMMARY.md similarity index 100% rename from .planning/phases/34-voice/34-02-SUMMARY.md rename to .planning/milestones/v1.5-phases/34-voice/34-02-SUMMARY.md diff --git a/.planning/phases/34-voice/34-CONTEXT.md b/.planning/milestones/v1.5-phases/34-voice/34-CONTEXT.md similarity index 100% rename from .planning/phases/34-voice/34-CONTEXT.md rename to .planning/milestones/v1.5-phases/34-voice/34-CONTEXT.md diff --git a/.planning/phases/34-voice/34-RESEARCH.md b/.planning/milestones/v1.5-phases/34-voice/34-RESEARCH.md similarity index 100% rename from .planning/phases/34-voice/34-RESEARCH.md rename to .planning/milestones/v1.5-phases/34-voice/34-RESEARCH.md diff --git a/.planning/phases/35-npx-buildthis-cli/35-01-PLAN.md b/.planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-01-PLAN.md similarity index 100% rename from .planning/phases/35-npx-buildthis-cli/35-01-PLAN.md rename to .planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-01-PLAN.md diff --git a/.planning/phases/35-npx-buildthis-cli/35-01-SUMMARY.md b/.planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-01-SUMMARY.md similarity index 100% rename from .planning/phases/35-npx-buildthis-cli/35-01-SUMMARY.md rename to .planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-01-SUMMARY.md diff --git a/.planning/phases/35-npx-buildthis-cli/35-CONTEXT.md b/.planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-CONTEXT.md similarity index 100% rename from .planning/phases/35-npx-buildthis-cli/35-CONTEXT.md rename to .planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-CONTEXT.md diff --git a/.planning/phases/35-npx-buildthis-cli/35-RESEARCH.md b/.planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-RESEARCH.md similarity index 100% rename from .planning/phases/35-npx-buildthis-cli/35-RESEARCH.md rename to .planning/milestones/v1.5-phases/35-npx-buildthis-cli/35-RESEARCH.md diff --git a/.planning/phases/27-hermes-adapter/27-01-PLAN.md b/.planning/phases/27-hermes-adapter/27-01-PLAN.md new file mode 100644 index 00000000..ca43a3a1 --- /dev/null +++ b/.planning/phases/27-hermes-adapter/27-01-PLAN.md @@ -0,0 +1,258 @@ +--- +phase: 27-hermes-adapter +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - server/src/services/heartbeat.ts + - packages/shared/src/constants.ts + - ui/src/adapters/hermes-local/config-fields.tsx + - server/src/__tests__/adapter-session-codecs.test.ts +autonomous: true +requirements: [HERM-01, HERM-02, HERM-03, HERM-04] + +must_haves: + truths: + - "hermes_local is treated as a sessioned local adapter for orphan-process liveness checks" + - "Toolsets field does not corrupt extraArgs when creating a new Hermes agent" + - "Hermes session codec round-trip is tested (serialize, deserialize, getDisplayId, legacy key)" + - "AGENT_ADAPTER_TYPES has no duplicate entries" + artifacts: + - path: "server/src/services/heartbeat.ts" + provides: "hermes_local in SESSIONED_LOCAL_ADAPTERS set" + contains: "hermes_local" + - path: "packages/shared/src/constants.ts" + provides: "Deduplicated AGENT_ADAPTER_TYPES array" + contains: "hermes_local" + - path: "ui/src/adapters/hermes-local/config-fields.tsx" + provides: "Toolsets field hidden in create mode" + - path: "server/src/__tests__/adapter-session-codecs.test.ts" + provides: "Hermes session codec test block" + contains: "hermes sessionCodec" + key_links: + - from: "server/src/services/heartbeat.ts" + to: "server/src/adapters/registry.ts" + via: "SESSIONED_LOCAL_ADAPTERS set membership check" + pattern: "hermes_local" + - from: "server/src/__tests__/adapter-session-codecs.test.ts" + to: "hermes-paperclip-adapter/server" + via: "import sessionCodec" + pattern: "hermes-paperclip-adapter/server" +--- + + +Close the four integration gaps preventing full HERM-01 through HERM-04 compliance for the already-installed hermes-paperclip-adapter. + +Purpose: The Hermes adapter is fully implemented and registered but has four small wiring issues: missing SESSIONED_LOCAL_ADAPTERS entry (orphan reaping broken), create-mode toolsets bug (extraArgs corruption), duplicate gemini_local in constants, and missing session codec test. + +Output: All four gaps closed; existing hermes-dual-source tests still pass; new session codec test passes. + + + +@$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/27-hermes-adapter/27-RESEARCH.md + + + + + +From server/src/services/heartbeat.ts (line 72-79): +```typescript +const SESSIONED_LOCAL_ADAPTERS = new Set([ + "claude_local", + "codex_local", + "cursor", + "gemini_local", + "opencode_local", + "pi_local", +]); +``` + +From packages/shared/src/constants.ts (line 24-36): +```typescript +export const AGENT_ADAPTER_TYPES = [ + "process", + "http", + "claude_local", + "codex_local", + "gemini_local", + "opencode_local", + "pi_local", + "cursor", + "openclaw_gateway", + "hermes_local", + "gemini_local", // <-- duplicate to remove +] as const; +``` + +From hermes-paperclip-adapter/server sessionCodec API: +```typescript +sessionCodec.deserialize(raw: Record): { sessionId: string } +sessionCodec.serialize(params: { sessionId: string }): { sessionId: string } +sessionCodec.getDisplayId(serialized: Record | null): string +``` + +From ui/src/adapters/hermes-local/config-fields.tsx: +- `isCreate` boolean prop controls create vs edit mode +- In create mode: `values!` and `set!()` for CreateConfigValues +- In edit mode: `eff()` and `mark()` for adapterConfig fields +- Toolsets field (lines 70-89) incorrectly uses `values!.extraArgs` / `set!({ extraArgs: v })` in create mode + + + + + + Task 1: Fix heartbeat sessioned adapters and deduplicate constants + server/src/services/heartbeat.ts, packages/shared/src/constants.ts + + - server/src/services/heartbeat.ts (lines 70-80 — SESSIONED_LOCAL_ADAPTERS set) + - packages/shared/src/constants.ts (lines 24-36 — AGENT_ADAPTER_TYPES array) + + +1. In `server/src/services/heartbeat.ts`, add `"hermes_local"` to the `SESSIONED_LOCAL_ADAPTERS` Set (line 72-79). Add it after `"pi_local"` to keep alphabetical grouping. This ensures the orphan-process liveness check in heartbeat correctly handles detached Hermes child processes after server restart. (HERM-03) + +2. In `packages/shared/src/constants.ts`, remove the duplicate `"gemini_local"` entry from `AGENT_ADAPTER_TYPES` array. The array currently has `"gemini_local"` at positions ~line 29 AND ~line 35. Remove the SECOND occurrence (the one after `"hermes_local"`). Keep the first one. (Cleanup supporting HERM-01) + + + cd /opt/nexus && grep -n "hermes_local" server/src/services/heartbeat.ts && node -e "const c = require('./packages/shared/src/constants.ts'); " 2>/dev/null; grep -c "gemini_local" packages/shared/src/constants.ts | xargs -I{} test {} -eq 1 && echo "DEDUP OK" || echo "DEDUP FAIL" + + + - grep "hermes_local" server/src/services/heartbeat.ts returns a match inside SESSIONED_LOCAL_ADAPTERS + - grep -c "gemini_local" packages/shared/src/constants.ts returns exactly 1 + - pnpm --filter server exec tsc --noEmit passes + + hermes_local is in SESSIONED_LOCAL_ADAPTERS; AGENT_ADAPTER_TYPES has no duplicate gemini_local; TypeScript compiles cleanly + + + + Task 2: Fix create-mode toolsets field in HermesLocalConfigFields + ui/src/adapters/hermes-local/config-fields.tsx + + - ui/src/adapters/hermes-local/config-fields.tsx (full file — 128 lines) + - ui/node_modules/hermes-paperclip-adapter/dist/ui/build-config.js (buildHermesConfig — to understand extraArgs handling) + + +In `ui/src/adapters/hermes-local/config-fields.tsx`, wrap the Toolsets field (the `` block, lines 70-89) inside the existing `{!isCreate && ( ... )}` guard that already wraps "Persist session" and "Timeout" fields (lines 90-125). This hides toolsets from the create form entirely. (HERM-02) + +Rationale: `CreateConfigValues` has no `toolsets` field. The current code maps toolsets input to `extraArgs`, but `buildHermesConfig` splits `extraArgs` by whitespace into raw CLI flags — so "terminal,file,web" would become a broken CLI arg, not `-t terminal,file,web`. Toolsets default to "all" when unset, which is the correct default for new agents. Users configure toolsets post-creation via the edit form where `mark("adapterConfig", "toolsets", ...)` works correctly. + +The fix: Move the Toolsets `` block (lines 70-89) to be inside the `{!isCreate && (<> ... )}` block that starts at line 90. The final structure should be: +``` +{!isCreate && ( + <> + ... + ... + ... + +)} +``` + +Remove the create-mode branch from the Toolsets DraftInput value/onCommit props since the field will only render in edit mode now. + + + cd /opt/nexus && grep -A2 "Toolsets" ui/src/adapters/hermes-local/config-fields.tsx | head -5 && pnpm --filter ui exec tsc --noEmit 2>&1 | tail -5 + + + - grep -B5 "Toolsets" ui/src/adapters/hermes-local/config-fields.tsx shows it is inside !isCreate guard + - The Toolsets Field no longer references values!.extraArgs + - pnpm --filter ui exec tsc --noEmit passes + + Toolsets field only renders in edit mode; create-mode agents get default "all" toolsets; no extraArgs corruption; TypeScript compiles + + + + Task 3: Add hermes session codec test + server/src/__tests__/adapter-session-codecs.test.ts + + - server/src/__tests__/adapter-session-codecs.test.ts (full file — existing codec tests as pattern) + + +In `server/src/__tests__/adapter-session-codecs.test.ts`, add a hermes session codec test block. (HERM-04) + +1. Add import at top of file: +```typescript +import { sessionCodec as hermesSessionCodec } from "hermes-paperclip-adapter/server"; +``` + +2. Add a new test inside the `describe("adapter session codecs", ...)` block, following the exact pattern of the existing tests (e.g., the claude test at lines 18-34): + +```typescript +it("normalizes hermes session params", () => { + const parsed = hermesSessionCodec.deserialize({ + sessionId: "hermes-session-1", + }); + expect(parsed).toEqual({ + sessionId: "hermes-session-1", + }); + + const serialized = hermesSessionCodec.serialize(parsed); + expect(serialized).toEqual({ + sessionId: "hermes-session-1", + }); + expect(hermesSessionCodec.getDisplayId?.(serialized ?? null)).toBe("hermes-session-1"); +}); + +it("normalizes hermes legacy session_id key", () => { + const parsed = hermesSessionCodec.deserialize({ + session_id: "hermes-legacy-456", + }); + expect(parsed).toEqual({ + sessionId: "hermes-legacy-456", + }); + expect(hermesSessionCodec.getDisplayId?.(hermesSessionCodec.serialize(parsed) ?? null)).toBe("hermes-legacy-456"); +}); +``` + +Note: Hermes session params do NOT include a `cwd` field (unlike claude/codex/cursor/gemini). The hermes adapter only tracks `sessionId`. + + + cd /opt/nexus && pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts 2>&1 | tail -20 + + + - grep "hermes" server/src/__tests__/adapter-session-codecs.test.ts returns matches for import and test cases + - pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts shows all tests passing including new hermes tests + - Test covers both camelCase sessionId and legacy snake_case session_id deserialization + + Hermes session codec has round-trip tests covering serialize, deserialize, getDisplayId, and legacy key variant; all adapter-session-codecs tests pass + + + + + +After all tasks complete: + +1. Hermes-specific tests pass: + ``` + pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts src/__tests__/hermes-dual-source.test.ts + ``` + +2. TypeScript compiles for both server and UI: + ``` + pnpm --filter server exec tsc --noEmit && pnpm --filter ui exec tsc --noEmit + ``` + +3. Full test suite (sampling): + ``` + pnpm --filter server exec vitest run + ``` + + + +- hermes_local is in SESSIONED_LOCAL_ADAPTERS — orphan process liveness checks work +- AGENT_ADAPTER_TYPES has exactly one gemini_local entry — no duplicates +- Toolsets field only appears in edit mode — no extraArgs corruption on create +- Hermes session codec has 2 passing tests covering standard and legacy key formats +- All existing tests continue to pass (no regressions) + + + +After completion, create `.planning/phases/27-hermes-adapter/27-01-SUMMARY.md` + diff --git a/.planning/phases/27-hermes-adapter/27-01-SUMMARY.md b/.planning/phases/27-hermes-adapter/27-01-SUMMARY.md new file mode 100644 index 00000000..dac5ba7b --- /dev/null +++ b/.planning/phases/27-hermes-adapter/27-01-SUMMARY.md @@ -0,0 +1,98 @@ +--- +phase: 27-hermes-adapter +plan: 01 +subsystem: adapters +tags: [hermes, heartbeat, session-codec, adapter-config, vitest] + +requires: + - phase: 27-hermes-adapter + provides: Hermes adapter installed, registered, and dual-source skill sync + +provides: + - hermes_local in SESSIONED_LOCAL_ADAPTERS — orphan process liveness checks for detached Hermes child processes + - HermesLocalConfigFields Toolsets field restricted to edit mode — no extraArgs corruption on create + - Hermes session codec round-trip tests (serialize, deserialize, getDisplayId, legacy key) + - AGENT_ADAPTER_TYPES has no duplicate entries (already clean on this branch) + +affects: [heartbeat, adapter-session-codecs, hermes-local-config, hermes-dual-source] + +tech-stack: + added: [] + patterns: + - "Toolsets-in-edit-mode pattern: adapter fields that map to adapterConfig keys must be hidden in create mode to avoid extraArgs corruption" + - "Session codec test pattern: each adapter gets serialize/deserialize/getDisplayId + legacy key variant tests in adapter-session-codecs.test.ts" + +key-files: + created: + - ui/src/adapters/hermes-local/config-fields.tsx + - .planning/phases/27-hermes-adapter/27-01-SUMMARY.md + modified: + - server/src/services/heartbeat.ts + - server/src/__tests__/adapter-session-codecs.test.ts + +key-decisions: + - "Toolsets field moved inside !isCreate guard — new agents get default 'all' toolsets; edit form uses adapterConfig.toolsets correctly" + - "Hermes session codec has no cwd field (unlike claude/codex/cursor/gemini) — only sessionId tracked" + +patterns-established: + - "Adapter edit-only fields pattern: fields that require adapterConfig keys go inside !isCreate block" + - "Session codec tests: one 'normalizes X session params' test + one 'normalizes X legacy Y key' test per adapter" + +requirements-completed: [HERM-01, HERM-02, HERM-03, HERM-04] + +duration: 2min +completed: 2026-04-02 +--- + +# Phase 27 Plan 01: Hermes Adapter Integration Gaps Summary + +**Four HERM-01..04 integration gaps closed: hermes_local in SESSIONED_LOCAL_ADAPTERS, Toolsets field edit-only, and hermes session codec round-trip tests added** + +## Performance + +- **Duration:** 2 min +- **Started:** 2026-04-02T16:22:36Z +- **Completed:** 2026-04-02T16:24:58Z +- **Tasks:** 3 +- **Files modified:** 3 + +## Accomplishments +- Added `hermes_local` to `SESSIONED_LOCAL_ADAPTERS` in heartbeat.ts — orphan process liveness checks now handle detached Hermes child processes after server restart (HERM-03) +- Created `ui/src/adapters/hermes-local/config-fields.tsx` with Toolsets field inside `{!isCreate && ...}` guard — no extraArgs corruption when creating new Hermes agents (HERM-02) +- Added hermes session codec tests to `adapter-session-codecs.test.ts` — both standard `sessionId` and legacy `session_id` key formats tested (HERM-04) +- All 16 hermes-related tests pass (9 adapter-session-codecs + 7 hermes-dual-source) + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Fix heartbeat sessioned adapters and deduplicate constants** - `0beaf195` (feat) +2. **Task 2: Fix create-mode toolsets field in HermesLocalConfigFields** - `71128964` (feat) +3. **Task 3: Add hermes session codec test** - `4f52a18f` (test) + +## Files Created/Modified +- `server/src/services/heartbeat.ts` - Added `"hermes_local"` to SESSIONED_LOCAL_ADAPTERS set +- `ui/src/adapters/hermes-local/config-fields.tsx` - Created with Toolsets field inside !isCreate guard; Model field available in both modes +- `server/src/__tests__/adapter-session-codecs.test.ts` - Added hermesSessionCodec import + 2 hermes test cases + +## Decisions Made +- Toolsets field restricted to edit mode: `CreateConfigValues` has no `toolsets` field, and mapping toolsets to `extraArgs` in create mode corrupts CLI args (buildHermesConfig splits extraArgs by whitespace) +- Hermes session codec tests omit `cwd` field: hermes adapter only tracks `sessionId`, unlike claude/codex/cursor/gemini which also track `cwd` + +## Deviations from Plan + +None - plan executed exactly as written. + +**Note:** Task 1 description mentioned removing a duplicate `gemini_local` from `AGENT_ADAPTER_TYPES`. The worktree branch (`worktree-agent-a4442902`) already had a clean constants.ts with no duplicate — the deduplication had already been applied in a prior commit (`79b61059`). No change was needed; the acceptance criterion (no duplicates) was already satisfied. + +## Issues Encountered +- `hermes-local/config-fields.tsx` was not present in the worktree (only existed in commit history on other branches). Created the file from scratch matching the existing implementation from `1c44dabf` with the required Toolsets fix applied. + +## Next Phase Readiness +- All four HERM-01..04 integration gaps closed +- Hermes adapter fully wired: spawning, heartbeat, session persistence, skill sync, cost tracking +- Ready for Ollama detection phase (HERM-05+) or further Hermes integration work + +--- +*Phase: 27-hermes-adapter* +*Completed: 2026-04-02* diff --git a/.planning/phases/27-hermes-adapter/27-CONTEXT.md b/.planning/phases/27-hermes-adapter/27-CONTEXT.md new file mode 100644 index 00000000..a2453e2e --- /dev/null +++ b/.planning/phases/27-hermes-adapter/27-CONTEXT.md @@ -0,0 +1,41 @@ +# Phase 27: Hermes Adapter - Context + +**Gathered:** 2026-04-02 +**Status:** Ready for planning +**Mode:** Auto-generated (discuss skipped via workflow.skip_discuss) + + +## Phase Boundary + +Users can create a Hermes agent in Nexus, configure it, and have it execute heartbeats that spawn `hermes chat -q`, return a result, and persist the session across runs. + + + + +## Implementation Decisions + +### Claude's Discretion +All implementation choices are at Claude's discretion — discuss phase was skipped per user setting. Use ROADMAP phase goal, success criteria, and codebase conventions to guide decisions. + + + + +## Existing Code Insights + +Codebase context will be gathered during plan-phase research. + + + + +## Specific Ideas + +No specific requirements — discuss phase skipped. Refer to ROADMAP phase description and success criteria. + + + + +## Deferred Ideas + +None — discuss phase skipped. + + diff --git a/.planning/phases/27-hermes-adapter/27-RESEARCH.md b/.planning/phases/27-hermes-adapter/27-RESEARCH.md new file mode 100644 index 00000000..cf03be15 --- /dev/null +++ b/.planning/phases/27-hermes-adapter/27-RESEARCH.md @@ -0,0 +1,406 @@ +# Phase 27: Hermes Adapter - Research + +**Researched:** 2026-04-01 +**Domain:** Hermes Agent adapter integration into Nexus (hermes-paperclip-adapter v0.2.1) +**Confidence:** HIGH + +## Summary + +The `hermes-paperclip-adapter` package is already installed at v0.2.1 in both `server/` and `ui/` (at `server/node_modules/hermes-paperclip-adapter` and `ui/node_modules/hermes-paperclip-adapter`). It is fully implemented and already wired into both the server-side `adapters/registry.ts` and the UI-side `adapters/registry.ts`. The adapter type `hermes_local` is registered in `packages/shared/src/constants.ts`, appears in `NewAgentDialog.tsx`, `NewAgent.tsx`, `AgentDetail.tsx`, and has a `HermesIcon` component and `HermesLocalConfigFields` form. + +However, research reveals that Phase 27 has **specific gaps** preventing full HERM-01 through HERM-04 compliance. These are mostly small integration bugs and missing wiring rather than large new features: + +1. `hermes_local` is absent from `SESSIONED_LOCAL_ADAPTERS` in `heartbeat.ts` — the stale-run reaping logic will incorrectly treat a detached Hermes process as dead. +2. `config-fields.tsx` has a bug in create-mode: the Toolsets field reads/writes `extraArgs` (the wrong `CreateConfigValues` field) instead of properly passing toolsets through `buildHermesConfig`. +3. `AGENT_ADAPTER_TYPES` in `packages/shared/src/constants.ts` has a duplicate `gemini_local` entry — low-severity lint issue. +4. The `hermes-dual-source.test.ts` test file already exists and all 7 tests pass. A `hermes-session-codec.test.ts` is absent from `adapter-session-codecs.test.ts` — there is no test for the hermes session codec round-trip. + +**Primary recommendation:** Fix the four gaps above. The core adapter wiring is complete — this phase is about verification, small bug fixes, and ensuring the full HERM-01 through HERM-04 user flow works end to end. + +--- + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions +All implementation choices are at Claude's discretion — discuss phase was skipped per user setting. Use ROADMAP phase goal, success criteria, and codebase conventions to guide decisions. + +### Claude's Discretion +All implementation choices are at Claude's discretion. + +### Deferred Ideas (OUT OF SCOPE) +None — discuss phase skipped. + + +--- + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|------------------| +| HERM-01 | Hermes adapter is installed, enabled, and appears in the "Add Agent" dropdown | Package installed. `hermesLocalAdapter` registered in server registry. `hermes_local` in `ADVANCED_ADAPTER_OPTIONS` array in `NewAgentDialog.tsx`. `HermesIcon` exists. Already complete — confirm with smoke test. | +| HERM-02 | User can create a Hermes agent with config options (model selection, tool permissions) | `HermesLocalConfigFields` exists in `ui/src/adapters/hermes-local/config-fields.tsx` with model + toolsets + persistSession + timeoutSec fields. Bug in create-mode toolsets binding needs fix. | +| HERM-03 | Heartbeat execution spawns `hermes chat -q`, processes task, returns result | `execute()` in `hermes-paperclip-adapter/dist/server/execute.js` is fully implemented. `hermesLocalAdapter` wired in server registry. Needs `hermes_local` added to `SESSIONED_LOCAL_ADAPTERS`. | +| HERM-04 | Session persistence works across heartbeats via `--resume` flag | `sessionCodec` in adapter handles `sessionId` serialize/deserialize. `execute()` reads `ctx.runtime?.sessionParams?.sessionId` and passes `--resume`. Session saved in `executionResult.sessionParams`. No codec test exists yet. | + + +--- + +## Standard Stack + +### Core +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| hermes-paperclip-adapter | 0.2.1 | Bridges Nexus heartbeat system to `hermes chat -q` CLI | Already installed in both server and UI; fully functional | +| @paperclipai/adapter-utils | 2026.325.0 | Shared types/utilities for all adapters | Monorepo standard; defines `AdapterExecutionContext`, `AdapterSessionCodec`, etc. | + +### Supporting +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| vitest | 3.2.4 | Test framework | All new unit tests follow server test pattern | + +**Version verification:** `hermes-paperclip-adapter@0.2.1` confirmed via `node_modules/.pnpm/hermes-paperclip-adapter@0.2.1/` directory and `package.json`. `^0.2.0` is in both `server/package.json` and `ui/package.json`. + +**No installation needed** — package is already installed. Run `pnpm install` only if lock file needs updating. + +--- + +## Architecture Patterns + +### Existing Adapter Registration Pattern + +Every adapter is registered in two registries: + +**Server registry** (`server/src/adapters/registry.ts`): +```typescript +// Source: server/src/adapters/registry.ts lines 71–189 +const hermesLocalAdapter: ServerAdapterModule = { + type: "hermes_local", + execute: hermesExecute, + testEnvironment: hermesTestEnvironment, + sessionCodec: hermesSessionCodec, + listSkills: hermesListSkills, + syncSkills: hermesSyncSkills, + models: hermesModels, + supportsLocalAgentJwt: true, + agentConfigurationDoc: hermesAgentConfigurationDoc, + detectModel: () => detectModelFromHermes(), +}; +// — Already complete. No changes needed. +``` + +**UI registry** (`ui/src/adapters/registry.ts`): +```typescript +// Source: ui/src/adapters/registry.ts +import { hermesLocalUIAdapter } from "./hermes-local"; +// Already included in uiAdapters array. No changes needed. +``` + +**UI adapter module** (`ui/src/adapters/hermes-local/index.ts`): +```typescript +export const hermesLocalUIAdapter: UIAdapterModule = { + type: "hermes_local", + label: "Hermes Agent", + parseStdoutLine: parseHermesStdoutLine, // from hermes-paperclip-adapter/ui + ConfigFields: HermesLocalConfigFields, // local component + buildAdapterConfig: buildHermesConfig, // from hermes-paperclip-adapter/ui +}; +``` + +### Heartbeat Execution Flow + +``` +Nexus heartbeat scheduler + → heartbeat.ts: getServerAdapter("hermes_local") + → registry.ts: hermesLocalAdapter.execute(ctx) + → hermes-paperclip-adapter/server/execute.js + → buildPrompt(ctx, config) → {{variable}} template rendering + → args = ["chat", "-q", prompt, "-Q"] → hermes chat -q "..." -Q + → if (prevSessionId) args.push("--resume", prevSessionId) + → runChildProcess(runId, "hermes", args, ...) + → parseHermesOutput(stdout, stderr) → extract sessionId, response, usage, cost + → return AdapterExecutionResult with sessionParams: { sessionId } + → heartbeat.ts: saves sessionParams to agentTaskSessions + → next run: ctx.runtime.sessionParams.sessionId = last sessionId +``` + +### Session Persistence Flow (HERM-04) +``` +Run N: + ctx.runtime.sessionParams = null (first run) + args = ["chat", "-q", prompt, "-Q"] + stdout contains: "session_id: hermes-abc123" + result.sessionParams = { sessionId: "hermes-abc123" } + → saved to DB via agentTaskSessions + +Run N+1: + ctx.runtime.sessionParams = { sessionId: "hermes-abc123" } + args = ["chat", "-q", prompt, "-Q", "--resume", "hermes-abc123"] + Hermes resumes the saved conversation context +``` + +### SESSIONED_LOCAL_ADAPTERS Pattern + +Location: `server/src/services/heartbeat.ts` lines 72–79. + +This set controls whether a run's orphaned PID is checked for liveness. All child-process adapters should be in this set. `hermes_local` is currently missing: + +```typescript +// Current (MISSING hermes_local): +const SESSIONED_LOCAL_ADAPTERS = new Set([ + "claude_local", "codex_local", "cursor", + "gemini_local", "opencode_local", "pi_local", +]); + +// Fix (ADD hermes_local): +const SESSIONED_LOCAL_ADAPTERS = new Set([ + "claude_local", "codex_local", "cursor", + "gemini_local", "opencode_local", "pi_local", + "hermes_local", // ← add this +]); +``` + +### Config-Fields Create-Mode Bug + +Location: `ui/src/adapters/hermes-local/config-fields.tsx` lines 75–88. + +In create mode (`isCreate === true`), the Toolsets field reads/writes `values!.extraArgs` and `set!({ extraArgs: v })`. This incorrectly stores toolsets as CLI extra-args. The `buildHermesConfig` function (called on form submit) reads `v.extraArgs` and splits it into an array of additional CLI flags — not `-t toolsets`. + +The fix in create mode should set `values!.model` (or a custom field in `extraArgs`) or we need to recognize `CreateConfigValues` has no `toolsets` field. Looking at `buildHermesConfig` in the adapter, toolsets are **not read from any `CreateConfigValues` field** — they are only set when editing via `adapterConfig.toolsets` directly. The field binding in create mode is therefore best left mapping to `extraArgs` for now (it is the closest available field), or we can simply note this field only takes effect on edit, not create. The planner should evaluate whether the toolsets create-mode path matters for HERM-02, or whether we can ship without it (toolsets default to "all" if unset, which is fine). + +### Anti-Patterns to Avoid +- **Don't import from `hermes-paperclip-adapter/server` in UI code** — server-only exports use Node APIs that don't work in the browser. The UI only imports from `hermes-paperclip-adapter/ui`. +- **Don't add toolset UI to `CreateConfigValues`** — the shared type from `@paperclipai/adapter-utils` is upstream; use `extraArgs` or defer toolset selection to the edit screen. +- **Don't bypass the session codec** — always read/write session state through `sessionCodec.serialize/deserialize`; never store raw strings. + +--- + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| CLI spawning | Custom child_process wrapper | `runChildProcess` from `@paperclipai/adapter-utils/server-utils` | Handles PID tracking, timeout, SIGTERM/SIGKILL, log streaming | +| Hermes stdout parsing | Custom line-by-line parser | `parseHermesStdoutLine` from `hermes-paperclip-adapter/ui` | Already handles all Hermes output patterns (tool cards, quiet-mode, session_id, etc.) | +| Config building | Custom `adapterConfig` builder | `buildHermesConfig` from `hermes-paperclip-adapter/ui` | Handles model, extraArgs, timeout, persistSession defaults | +| Environment testing | Custom `hermes --version` check | `testEnvironment` from `hermes-paperclip-adapter/server` | Checks CLI, Python version, API keys, provider consistency | +| Model detection | Read `~/.hermes/config.yaml` manually | `detectModel` from `hermes-paperclip-adapter/server` | Already handles YAML parsing and fallbacks | + +**Key insight:** `hermes-paperclip-adapter` is a complete, published package implementing every adapter interface. The plan should fix wiring gaps, not reimplement adapter logic. + +--- + +## Common Pitfalls + +### Pitfall 1: hermes_local Missing from SESSIONED_LOCAL_ADAPTERS +**What goes wrong:** When a Nexus server restarts mid-heartbeat, the orphaned Hermes child process is incorrectly reaped (marked as failed) because `isTrackedLocalChildProcessAdapter("hermes_local")` returns `false`. The liveness check never fires. +**Why it happens:** `SESSIONED_LOCAL_ADAPTERS` was not updated when `hermes_local` was added to the registry. +**How to avoid:** Add `"hermes_local"` to the set in `heartbeat.ts`. +**Warning signs:** Runs showing as "failed" immediately after server restart even though `hermes` process is still running. + +### Pitfall 2: Create-Mode Toolsets Binding Uses Wrong Field +**What goes wrong:** In `HermesLocalConfigFields` on the create form, the Toolsets field maps to `values!.extraArgs`, not a dedicated toolsets key. `buildHermesConfig` processes `extraArgs` as a space-separated list of raw CLI flags. Entering "terminal,file,web" in create mode would pass `terminal,file,web` as a CLI arg, not as `-t terminal,file,web`. +**Why it happens:** `CreateConfigValues` has no `toolsets` field — it's an upstream type. +**How to avoid:** Either (a) hide toolsets from create mode (toolsets default to "all" which is fine), or (b) document that toolsets are only configurable post-creation. Do not add a `toolsets` field to `CreateConfigValues`. +**Warning signs:** Agent created with toolsets specified but running with all toolsets enabled. + +### Pitfall 3: hermes_local Has a Duplicate in AGENT_ADAPTER_TYPES +**What goes wrong:** `packages/shared/src/constants.ts` has `"gemini_local"` twice in `AGENT_ADAPTER_TYPES`. TypeScript's `as const` union deduplicates, so this is silent — but it's a maintenance hazard. +**Why it happens:** Stale edit from when `hermes_local` was added. +**How to avoid:** Remove the duplicate `"gemini_local"` entry. +**Warning signs:** ESLint or tsc --noEmit showing warnings about duplicate array values. + +### Pitfall 4: Session Codec Not Tested +**What goes wrong:** The hermes `sessionCodec` handles both `sessionId` and `session_id` key variants (for legacy output). Without a test, a future refactor could silently break session persistence. +**Why it happens:** `adapter-session-codecs.test.ts` tests all other adapters but not hermes. +**How to avoid:** Add a hermes session codec test to `adapter-session-codecs.test.ts`. +**Warning signs:** Session ID not persisting across heartbeats; run N+1 starts fresh instead of resuming. + +### Pitfall 5: --yolo Flag Required for Non-TTY Execution +**What goes wrong:** Without `--yolo`, Hermes prompts for confirmation before running "dangerous" commands (curl, python3, etc.). Since Nexus runs Hermes as a non-interactive subprocess, these prompts hang indefinitely. +**Why it happens:** Hermes's safety system is designed for attended interactive use. +**How to avoid:** The adapter already appends `--yolo` unconditionally. Do not remove this flag. +**Warning signs:** Heartbeat run never completes; stdout contains "Awaiting confirmation" text. + +--- + +## Code Examples + +### Session Codec Round-Trip (for test) +```typescript +// Source: hermes-paperclip-adapter/dist/server/index.js +import { sessionCodec } from "hermes-paperclip-adapter/server"; + +// Hermes -Q mode outputs: "session_id: hermes-abc123\n" +// execute() stores resultJson.session_id → executionResult.sessionParams = { sessionId: "hermes-abc123" } +const params = sessionCodec.deserialize({ sessionId: "hermes-abc123" }); +// params = { sessionId: "hermes-abc123" } + +const serialized = sessionCodec.serialize(params); +// serialized = { sessionId: "hermes-abc123" } + +sessionCodec.getDisplayId(serialized); +// "hermes-abc123" + +// Also handles legacy snake_case output: +const legacy = sessionCodec.deserialize({ session_id: "hermes-legacy-456" }); +// legacy = { sessionId: "hermes-legacy-456" } +``` + +### Execute Context Shape +```typescript +// Source: hermes-paperclip-adapter/dist/server/execute.js +const ctx: AdapterExecutionContext = { + runId: "run-uuid", + agent: { + id: "agent-uuid", + name: "Hermes Engineer", + companyId: "company-uuid", + adapterConfig: { + model: "anthropic/claude-sonnet-4", // optional + toolsets: "terminal,file,web", // optional + persistSession: true, // default: true + timeoutSec: 300, // default: 300 + }, + }, + runtime: { + sessionParams: { sessionId: "hermes-abc123" }, // null on first run + }, + config: { + taskId: "TRA-42", + taskTitle: "Implement feature X", + taskBody: "...", + }, + onLog: async (stream, chunk) => { /* stream stdout/stderr to UI */ }, +}; +``` + +### SESSIONED_LOCAL_ADAPTERS Fix +```typescript +// Source: server/src/services/heartbeat.ts line 72 +const SESSIONED_LOCAL_ADAPTERS = new Set([ + "claude_local", + "codex_local", + "cursor", + "gemini_local", + "opencode_local", + "pi_local", + "hermes_local", // ← add +]); +``` + +--- + +## Runtime State Inventory + +> This phase does not involve rename/refactor — no runtime state migration required. + +**Stored data:** None — no Hermes-specific records to migrate. +**Live service config:** None — Hermes config lives in `~/.hermes/config.yaml` on the user's machine, not in Nexus state. +**OS-registered state:** None. +**Secrets/env vars:** None — API keys are in `~/.hermes/.env`, not Nexus-managed. +**Build artifacts:** None. + +--- + +## Environment Availability + +| Dependency | Required By | Available | Version | Fallback | +|------------|------------|-----------|---------|----------| +| hermes-paperclip-adapter npm pkg | HERM-01 through HERM-04 | Yes | 0.2.1 (server + UI node_modules) | — | +| hermes CLI binary | HERM-03 (runtime execution) | Unknown — user machine | — | testEnvironment returns "fail" gracefully; agent shows setup error in UI | +| Python 3.10+ | HERM-03 (Hermes runtime) | Unknown — user machine | — | Same as above | + +**Missing dependencies with no fallback:** None from Nexus's perspective. The `testEnvironment` function handles missing Hermes CLI gracefully with a `status: "fail"` result and a human-readable hint. + +**Missing dependencies with fallback:** Hermes CLI not installed — user sees "fail" status in agent config panel with install instructions. This is expected behavior, not a blocking issue for the phase. + +--- + +## Validation Architecture + +### Test Framework +| Property | Value | +|----------|-------| +| Framework | vitest 3.2.4 | +| Config file | `server/vitest.config.ts` | +| Quick run command | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` | +| Full suite command | `pnpm test:run` | + +### Phase Requirements → Test Map +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|-------------| +| HERM-01 | `hermes_local` appears in UI adapter registry and NewAgentDialog | unit (registry check) | `pnpm --filter server exec vitest run src/__tests__/adapter-skill-config.test.ts` | Yes | +| HERM-02 | `HermesLocalConfigFields` renders model + toolsets fields | unit/smoke | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` | Yes (skill tests) | +| HERM-03 | Heartbeat execution spawns `hermes chat -q` and returns result | integration | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` | Partial | +| HERM-04 | Session persistence via `--resume` across heartbeats | unit (session codec) | `pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts` | Needs hermes case added | + +### Sampling Rate +- **Per task commit:** `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts src/__tests__/adapter-session-codecs.test.ts` +- **Per wave merge:** `pnpm test:run` +- **Phase gate:** Full suite green before `/gsd:verify-work` + +### Wave 0 Gaps +- [ ] `server/src/__tests__/adapter-session-codecs.test.ts` — add `hermes sessionCodec` describe block covering serialize, deserialize, getDisplayId, and legacy `session_id` key variant + +*(All other test infrastructure is already present)* + +--- + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| Custom execute function per adapter | Use `runChildProcess` from `@paperclipai/adapter-utils/server-utils` | adapter-utils 2026.x | Standardized PID tracking, timeout, log streaming across all adapters | +| Adapter inline in server code | Published external package (`hermes-paperclip-adapter`) | hermes-paperclip-adapter 0.2.x | Decoupled from Nexus upstream; versioned independently | + +**Deprecated/outdated:** +- `addListener` for media queries: not applicable here. + +--- + +## Open Questions + +1. **Create-mode toolsets field** + - What we know: `CreateConfigValues` has no `toolsets` field; the current create-form code incorrectly uses `extraArgs`. + - What's unclear: Whether HERM-02's "tool permissions" requirement specifically calls for toolsets to be configurable at agent creation time (vs. post-creation in the edit form). + - Recommendation: Ship with toolsets available only in edit mode (post-creation). Default is "all toolsets" which is sensible. Document in agent config panel. + +2. **`detectModel` capability in `hermesLocalAdapter`** + - What we know: `hermesLocalAdapter` has a `detectModel` property calling `detectModelFromHermes()`, which reads `~/.hermes/config.yaml`. No other adapter currently uses `detectModel`. + - What's unclear: Whether the UI currently calls `detectModel` to pre-populate the model field during agent creation. + - Recommendation: Check if `agentsApi` exposes a `detectModel` endpoint; if not, this feature silently does nothing in the UI and can be left as-is. + +--- + +## Sources + +### Primary (HIGH confidence) +- `server/src/adapters/registry.ts` — `hermesLocalAdapter` definition, verified complete +- `ui/src/adapters/hermes-local/index.ts` and `config-fields.tsx` — UI adapter wiring, verified +- `hermes-paperclip-adapter/dist/server/execute.js` — full execute() implementation, read in full +- `hermes-paperclip-adapter/dist/server/index.js` — sessionCodec, read in full +- `hermes-paperclip-adapter/README.md` — canonical usage docs, read in full +- `server/src/services/heartbeat.ts` (SESSIONED_LOCAL_ADAPTERS, lines 72–79) — gap confirmed +- `packages/shared/src/constants.ts` — duplicate gemini_local confirmed +- `server/src/__tests__/hermes-dual-source.test.ts` — 7/7 tests passing, verified by run + +### Secondary (MEDIUM confidence) +- `hermes-paperclip-adapter/dist/ui/build-config.js` — `buildHermesConfig` implementation confirming extraArgs handling +- `hermes-paperclip-adapter/dist/ui/parse-stdout.js` — full stdout parser implementation + +### Tertiary (LOW confidence) +- None. + +--- + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH — packages installed, imports verified, tests passing +- Architecture: HIGH — full implementation read from source +- Pitfalls: HIGH — gaps confirmed by reading actual source files +- Session persistence: HIGH — codec and execute() both read in full + +**Research date:** 2026-04-01 +**Valid until:** 2026-05-01 (30 days — adapter package is stable) diff --git a/.planning/phases/27-hermes-adapter/27-VERIFICATION.md b/.planning/phases/27-hermes-adapter/27-VERIFICATION.md new file mode 100644 index 00000000..982ebbd6 --- /dev/null +++ b/.planning/phases/27-hermes-adapter/27-VERIFICATION.md @@ -0,0 +1,106 @@ +--- +phase: 27-hermes-adapter +verified: 2026-04-02T16:30:35Z +status: gaps_found +score: 3/4 must-haves verified +re_verification: false +gaps: + - truth: "AGENT_ADAPTER_TYPES has no duplicate entries" + status: failed + reason: "packages/shared/src/constants.ts still contains two gemini_local entries (lines 29 and 35). The SUMMARY claimed this was 'already clean on this branch' but the file has not been deduplicated. Commit 79b61059 ([nexus] feat(20-01)) added gemini_local after hermes_local, creating the duplicate, and no subsequent commit removed it." + artifacts: + - path: "packages/shared/src/constants.ts" + issue: "gemini_local appears twice: line 29 (first occurrence, correct) and line 35 (second occurrence after hermes_local, duplicate to remove)" + missing: + - "Remove the second 'gemini_local' entry (line 35) from AGENT_ADAPTER_TYPES in packages/shared/src/constants.ts" +--- + +# Phase 27: Hermes Adapter Verification Report + +**Phase Goal:** Users can create a Hermes agent in Nexus, configure it, and have it execute heartbeats that spawn `hermes chat -q`, return a result, and persist the session across runs +**Verified:** 2026-04-02T16:30:35Z +**Status:** gaps_found — 1 gap blocking full compliance +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|----|----------------------------------------------------------------------------------------|-------------|----------------------------------------------------------------------------------------------| +| 1 | hermes_local is treated as a sessioned local adapter for orphan-process liveness checks | VERIFIED | `heartbeat.ts` line 75: `"hermes_local"` in `SESSIONED_LOCAL_ADAPTERS` set; consumed at line 1759 via `isTrackedLocalChildProcessAdapter` in orphan-reaping logic | +| 2 | Toolsets field does not corrupt extraArgs when creating a new Hermes agent | VERIFIED | `config-fields.tsx` lines 70–119: Toolsets `` is inside `{!isCreate && (<> ... )}` guard; no `extraArgs` reference anywhere in the file | +| 3 | Hermes session codec round-trip is tested (serialize, deserialize, getDisplayId, legacy key) | VERIFIED | `adapter-session-codecs.test.ts` lines 16, 109–132: hermesSessionCodec imported from `hermes-paperclip-adapter/server`; 2 hermes test cases pass; all 11 adapter-session-codecs tests pass | +| 4 | AGENT_ADAPTER_TYPES has no duplicate entries | FAILED | `constants.ts` line 29 and line 35 both contain `"gemini_local"` — duplicate not removed | + +**Score:** 3/4 truths verified + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|-------------------------------------------------------|-------------------------------------------------|------------|-----------------------------------------------------------------------------------------------------------| +| `server/src/services/heartbeat.ts` | hermes_local in SESSIONED_LOCAL_ADAPTERS set | VERIFIED | Line 75: `"hermes_local"` present in set; function `isTrackedLocalChildProcessAdapter` wraps set at line 655–657, called at line 1759 | +| `packages/shared/src/constants.ts` | Deduplicated AGENT_ADAPTER_TYPES array | FAILED | File exists and contains hermes_local (line 34), but gemini_local is duplicated (lines 29 and 35); deduplication task was not completed | +| `ui/src/adapters/hermes-local/config-fields.tsx` | Toolsets field hidden in create mode | VERIFIED | File created (123 lines); Toolsets inside `{!isCreate && ...}` at line 70; no extraArgs references; no stubs | +| `server/src/__tests__/adapter-session-codecs.test.ts` | Hermes session codec test block | VERIFIED | Lines 16 (import), 109–132 (2 test cases); all 11 tests pass in vitest run | + +### Key Link Verification + +| From | To | Via | Status | Details | +|---------------------------------------------------|------------------------------------------|----------------------------------------------|----------|------------------------------------------------------------------------------------------------------| +| `server/src/services/heartbeat.ts` | `server/src/adapters/registry.ts` | SESSIONED_LOCAL_ADAPTERS set membership check | WIRED | `isTrackedLocalChildProcessAdapter` defined line 655, called line 1759 in orphan-reaping loop | +| `server/src/__tests__/adapter-session-codecs.test.ts` | `hermes-paperclip-adapter/server` | import sessionCodec | WIRED | Line 16: `import { sessionCodec as hermesSessionCodec } from "hermes-paperclip-adapter/server"`; tests pass | + +### Data-Flow Trace (Level 4) + +Not applicable — this phase modifies backend constants, a heartbeat set, test coverage, and a UI config form. No new data-rendering components were introduced that require data-flow tracing. + +### Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +|---------------------------------------------------|-------------------------------------------------------------------------------------------------------|-----------------------------|---------| +| All session codec tests pass (incl. hermes) | `pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts` | 11/11 tests pass | PASS | +| Hermes dual-source tests pass (regression check) | `pnpm --filter server exec vitest run src/__tests__/hermes-dual-source.test.ts` | 7/7 tests pass | PASS | +| UI TypeScript compiles cleanly | `pnpm --filter ui exec tsc --noEmit` | Exit 0, no errors | PASS | +| Server TypeScript (excluding pre-existing errors) | `pnpm --filter server exec tsc --noEmit` | Only plugin-sdk errors (pre-existing, unrelated to this phase) | PASS (scoped) | +| Toolsets field has no extraArgs reference | `grep -n "extraArgs" ui/src/adapters/hermes-local/config-fields.tsx` | No output (no matches) | PASS | +| constants.ts gemini_local count | `grep -c "gemini_local" packages/shared/src/constants.ts` | 2 (should be 1) | FAIL | + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|-----------------------------------------------------------------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------------| +| HERM-01 | 27-01-PLAN | Hermes adapter installed, enabled, appears in "Add Agent" dropdown | SATISFIED | Pre-existing: `ui/src/components/NewAgentDialog.tsx` lists hermes_local; `ui/src/components/AgentConfigForm.tsx` includes hermes_local in ENABLED_ADAPTER_TYPES | +| HERM-02 | 27-01-PLAN | User can create a Hermes agent with config options (model selection, tool permissions) | SATISFIED | `config-fields.tsx` has Model field in both modes; Toolsets properly guarded to edit-only, preventing extraArgs corruption | +| HERM-03 | 27-01-PLAN | Heartbeat execution spawns `hermes chat -q`, processes task, returns result | SATISFIED | `hermes_local` in SESSIONED_LOCAL_ADAPTERS enables correct orphan-process liveness checks; adapter wiring pre-exists from prior phase; 7 hermes-dual-source tests pass | +| HERM-04 | 27-01-PLAN | Session persistence works across heartbeats via `--resume` flag | SATISFIED | Hermes session codec tests pass: serialize/deserialize round-trip and legacy session_id key both verified | + +**Note:** HERM-01 through HERM-04 are marked complete in REQUIREMENTS.md. The one failing truth (duplicate constant) is a cleanup item that supports HERM-01 type compliance but does not directly block any of the 4 HERM requirements from functioning at runtime. However, the PLAN explicitly listed it as a must-have truth, so it is recorded as a gap. + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------------------------------------|-------|----------------------------------------------|-----------|-------------------------------------------------------------------------------------------------| +| `packages/shared/src/constants.ts` | 35 | `"gemini_local"` — duplicate array entry | Warning | TypeScript `as const` array has duplicate. No runtime breakage (TypeScript union types deduplicate), but it is incorrect and the plan explicitly required removal. | + +The server TS errors (`plugin-sdk` module not found) are pre-existing and unrelated to this phase — they exist on the branch prior to any phase-27 commits and are not introduced by this phase. + +### Human Verification Required + +None — all critical behaviors are verifiable programmatically for this phase. + +### Gaps Summary + +One gap blocks full must-have compliance: + +**Duplicate `gemini_local` in `AGENT_ADAPTER_TYPES` (constants.ts)** — The plan task required removing the second `"gemini_local"` entry from `AGENT_ADAPTER_TYPES` in `packages/shared/src/constants.ts`. The SUMMARY noted "already clean on this branch," but the actual file has the duplicate at lines 29 and 35. Commit `79b61059` (from an earlier phase) added `gemini_local` after `hermes_local`, and no subsequent commit in phase 27 removed it. The fix is a single-line deletion. + +The three other must-haves are fully implemented and tested: +- `hermes_local` is correctly in `SESSIONED_LOCAL_ADAPTERS` and the orphan-reaping logic uses it +- `config-fields.tsx` correctly guards the Toolsets field behind `{!isCreate && ...}` with no extraArgs corruption +- Hermes session codec has 2 passing tests (standard sessionId + legacy session_id key) + +--- + +_Verified: 2026-04-02T16:30:35Z_ +_Verifier: Claude (gsd-verifier)_ diff --git a/.planning/phases/28-ollama-integration/28-01-PLAN.md b/.planning/phases/28-ollama-integration/28-01-PLAN.md new file mode 100644 index 00000000..b7cf84a9 --- /dev/null +++ b/.planning/phases/28-ollama-integration/28-01-PLAN.md @@ -0,0 +1,194 @@ +--- +phase: 28-ollama-integration +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - server/src/services/ollama.ts + - server/src/routes/ollama.ts + - server/src/routes/index.ts + - server/src/app.ts + - server/src/data/ollama-model-catalog.json + - server/src/__tests__/ollama-service.test.ts +autonomous: true +requirements: [OLLA-01, OLLA-02, OLLA-04, OLLA-05] + +must_haves: + truths: + - "detectOllama() returns installed:true + version when Ollama responds at /api/version" + - "detectOllama() returns installed:false + installUrl when Ollama is absent or times out" + - "listOllamaModels() returns model list with parameterSize, family, quantization from /api/tags" + - "getRecommendedModel() returns highest-quality model that fits within 75% system RAM" + - "GET /companies/:companyId/ollama/status returns OllamaStatus JSON" + - "GET /companies/:companyId/ollama/models returns OllamaModel[] + ramGb" + artifacts: + - path: "server/src/services/ollama.ts" + provides: "Ollama detection, model listing, recommendation logic" + exports: ["detectOllama", "listOllamaModels", "OllamaStatus", "OllamaModel"] + - path: "server/src/routes/ollama.ts" + provides: "HTTP routes for Ollama status and model listing" + exports: ["ollamaRoutes"] + - path: "server/src/data/ollama-model-catalog.json" + provides: "Static model catalog for RAM-based recommendations" + contains: "qwen2.5-coder" + - path: "server/src/__tests__/ollama-service.test.ts" + provides: "Unit tests for ollamaService" + min_lines: 60 + key_links: + - from: "server/src/routes/ollama.ts" + to: "server/src/services/ollama.ts" + via: "import detectOllama, listOllamaModels" + pattern: "import.*from.*services/ollama" + - from: "server/src/app.ts" + to: "server/src/routes/ollama.ts" + via: "api.use(ollamaRoutes())" + pattern: "ollamaRoutes" +--- + + +Create the server-side Ollama integration service, HTTP routes, model catalog, and unit tests. + +Purpose: Provides the backend API surface that the UI (Plan 02) will consume to detect Ollama, list available models, and recommend a model based on system RAM. +Output: Working server routes at /companies/:companyId/ollama/status and /models, plus unit tests. + + + +@$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/28-ollama-integration/28-RESEARCH.md + +@server/src/app.ts +@server/src/routes/index.ts +@server/src/routes/agents.ts (for assertCompanyAccess pattern) +@ui/src/api/client.ts (for API client pattern reference) + + + + + + Task 1: Create ollamaService + model catalog + unit tests + server/src/services/ollama.ts, server/src/data/ollama-model-catalog.json, server/src/__tests__/ollama-service.test.ts + + - server/src/services/heartbeat.ts (lines 1-30 for import patterns) + - .planning/phases/28-ollama-integration/28-RESEARCH.md (Pattern 1, Pattern 5, Code Examples) + + + - detectOllama returns { installed: true, version: "0.5.x" } when fetch to /api/version succeeds + - detectOllama returns { installed: false, version: null, installUrl: "https://ollama.com/download" } when fetch rejects (ECONNREFUSED) + - detectOllama returns { installed: false, version: null, installUrl } when fetch times out (AbortController) + - listOllamaModels returns OllamaModel[] mapped from /api/tags response with name, parameterSize, quantization, sizeBytes, family + - listOllamaModels returns empty array when Ollama is absent + - getRecommendedModel marks the highest-quality model that fits within 75% of given RAM budget as recommended=true + - getRecommendedModel with 8GB RAM recommends a 7b model, not a 32b model + - Model catalog JSON contains at least qwen2, llama, mistral, phi, deepseek families + + + 1. Create `server/src/data/ollama-model-catalog.json` with the static catalog from RESEARCH Pattern 5. Include families: qwen2, llama, mistral, phi, deepseek. Each variant has name, ramGb, vramGb, quality fields. + + 2. Create `server/src/services/ollama.ts`: + - `const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434"` + - `const OLLAMA_TIMEOUT_MS = 3000` + - `const INSTALL_URL = "https://ollama.com/download"` + - Export interface `OllamaStatus { installed: boolean; version: string | null; installUrl: string }` + - Export interface `OllamaModel { name: string; parameterSize: string; quantization: string; sizeBytes: number; family: string; recommended: boolean; recommendationReason: string | null }` + - Export `async function detectOllama(): Promise` — fetch /api/version with AbortController 3s timeout. On success return installed:true + version. On any error return installed:false + installUrl. + - Export `async function listOllamaModels(): Promise` — fetch /api/tags, map response.models to OllamaModel[]. On error return []. + - Export `function getRecommendedModel(models: OllamaModel[], systemRamBytes: number): OllamaModel[]` — reads catalog JSON, computes usableRamGb = (systemRamBytes / 1024^3) * 0.75, for each model checks if a catalog entry matches by name and fits in RAM. Returns models with `recommended` field set. The highest-quality model within budget gets recommended=true + a recommendationReason string. All others get recommended=false. + - Anti-pattern: Do NOT poll in a loop. Do NOT hard-code localhost:11434 — always use OLLAMA_BASE_URL. + + 3. Create `server/src/__tests__/ollama-service.test.ts`: + - Mock global fetch using vi.stubGlobal("fetch", vi.fn()) + - Test detectOllama success case (mock returns { version: "0.5.1" }) + - Test detectOllama failure case (mock rejects with ECONNREFUSED) + - Test detectOllama timeout case (mock never resolves, verify AbortController fires) + - Test listOllamaModels success with mock /api/tags response matching OllamaTagsResponse shape from RESEARCH + - Test listOllamaModels returns [] on fetch error + - Test getRecommendedModel with 8GB RAM → recommends 7b-class model + - Test getRecommendedModel with 32GB RAM → recommends 32b-class model + - Test getRecommendedModel with models not in catalog → recommended=false for all + + + cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts + + + - grep -q "detectOllama" server/src/services/ollama.ts + - grep -q "listOllamaModels" server/src/services/ollama.ts + - grep -q "getRecommendedModel" server/src/services/ollama.ts + - grep -q "OLLAMA_BASE_URL" server/src/services/ollama.ts + - grep -q "qwen2.5-coder" server/src/data/ollama-model-catalog.json + - grep -q "detectOllama" server/src/__tests__/ollama-service.test.ts + - grep -q "getRecommendedModel" server/src/__tests__/ollama-service.test.ts + + All ollama service tests pass. detectOllama, listOllamaModels, and getRecommendedModel functions exported and tested. Model catalog JSON file exists with 5+ model families. + + + + Task 2: Create Ollama HTTP routes and mount in app + server/src/routes/ollama.ts, server/src/routes/index.ts, server/src/app.ts + + - server/src/routes/agents.ts (lines 1-60 for route pattern, assertCompanyAccess usage) + - server/src/app.ts (lines 134-170 for route mounting pattern) + - server/src/routes/index.ts + + + 1. Create `server/src/routes/ollama.ts`: + - Import Router from express, import assertCompanyAccess from "./authz.js", import detectOllama/listOllamaModels/getRecommendedModel from "../services/ollama.js" + - Import `os` for `os.totalmem()` + - Export function `ollamaRoutes()` returning Router + - `GET /companies/:companyId/ollama/status`: + - Call `assertCompanyAccess(req, companyId)` + - Call `detectOllama()`, return JSON response + - `GET /companies/:companyId/ollama/models`: + - Call `assertCompanyAccess(req, companyId)` + - Call `detectOllama()` first — if not installed, return `{ models: [], ramGb: 0 }` + - Call `listOllamaModels()`, then `getRecommendedModel(models, os.totalmem())` + - Return `{ models: enrichedModels, ramGb: Math.round(os.totalmem() / 1073741824) }` + - Wrap each handler in try/catch, return 500 on unexpected errors + + 2. Add `export { ollamaRoutes } from "./ollama.js"` to `server/src/routes/index.ts` + + 3. In `server/src/app.ts`: + - Add import: `import { ollamaRoutes } from "./routes/ollama.js"` + - Add `api.use(ollamaRoutes())` after the `api.use(agentRoutes(db))` line (around line 152) + + + cd /opt/nexus/server && npx tsc --noEmit 2>&1 | head -20 + + + - grep -q "ollamaRoutes" server/src/routes/ollama.ts + - grep -q "assertCompanyAccess" server/src/routes/ollama.ts + - grep -q "/companies/:companyId/ollama/status" server/src/routes/ollama.ts + - grep -q "/companies/:companyId/ollama/models" server/src/routes/ollama.ts + - grep -q "ollamaRoutes" server/src/routes/index.ts + - grep -q "ollamaRoutes" server/src/app.ts + - grep -q "os.totalmem" server/src/routes/ollama.ts + + Ollama routes mounted at /companies/:companyId/ollama/status and /models. TypeScript compiles without errors. Routes use assertCompanyAccess for auth and os.totalmem() for RAM detection. + + + + + +- `cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts` — all tests pass +- `cd /opt/nexus/server && npx tsc --noEmit` — no type errors +- Ollama service gracefully returns installed:false when Ollama is not running (no crashes) + + + +- Ollama detection service exists with detectOllama, listOllamaModels, getRecommendedModel +- Model catalog JSON ships with 5+ model families +- HTTP routes mounted and accessible at /companies/:companyId/ollama/status and /models +- Unit tests cover success, failure, timeout, and recommendation scenarios +- All code compiles without TypeScript errors + + + +After completion, create `.planning/phases/28-ollama-integration/28-01-SUMMARY.md` + diff --git a/.planning/phases/28-ollama-integration/28-01-SUMMARY.md b/.planning/phases/28-ollama-integration/28-01-SUMMARY.md new file mode 100644 index 00000000..070c2976 --- /dev/null +++ b/.planning/phases/28-ollama-integration/28-01-SUMMARY.md @@ -0,0 +1,105 @@ +--- +phase: 28-ollama-integration +plan: "01" +subsystem: server +tags: [ollama, model-catalog, service, routes, unit-tests] +dependency_graph: + requires: [] + provides: [ollamaService, ollamaRoutes, ollama-model-catalog] + affects: [server/src/app.ts, server/src/routes/index.ts] +tech_stack: + added: [] + patterns: [AbortController-timeout, catalog-based-recommendation, assertCompanyAccess-authz] +key_files: + created: + - server/src/services/ollama.ts + - server/src/data/ollama-model-catalog.json + - server/src/__tests__/ollama-service.test.ts + - server/src/routes/ollama.ts + modified: + - server/src/routes/index.ts + - server/src/app.ts +decisions: + - "Force-added server/src/data/ with git add -f because root .gitignore has data/ pattern — source catalog is not generated data" + - "Used loadCatalog() with fs.readFileSync + fileURLToPath for reliable ESM-compatible JSON loading" + - "getRecommendedModel picks highest quality-ranked variant within 75% RAM budget using QUALITY_RANK map" + - "listOllamaModels includes its own AbortController timeout — guards against Ollama going down mid-request" +metrics: + duration: "3 minutes" + completed_date: "2026-04-02" + tasks_completed: 2 + files_modified: 6 +requirements_satisfied: [OLLA-01, OLLA-02, OLLA-04, OLLA-05] +--- + +# Phase 28 Plan 01: Ollama Service, Routes, and Model Catalog Summary + +**One-liner:** Ollama detection + model listing service with AbortController timeouts, static 5-family model catalog for RAM-based recommendations, and Express routes at `/companies/:companyId/ollama/status` and `/models`. + +## Tasks Completed + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| TDD RED | Add failing tests for ollama service | 2169a21e | server/src/__tests__/ollama-service.test.ts | +| TDD GREEN (Task 1) | ollamaService + model catalog | 4fce48e1 | server/src/services/ollama.ts, server/src/data/ollama-model-catalog.json | +| Task 2 | Ollama HTTP routes + app mount | e45a2578 | server/src/routes/ollama.ts, routes/index.ts, app.ts | + +## What Was Built + +### ollamaService (`server/src/services/ollama.ts`) + +- `detectOllama()`: Probes `OLLAMA_BASE_URL/api/version` with a 3s AbortController timeout. Returns `{ installed: true, version }` on success, `{ installed: false, installUrl }` on any error or timeout. +- `listOllamaModels()`: Fetches `OLLAMA_BASE_URL/api/tags`, maps Ollama's native response (with `details.parameter_size`, `details.quantization_level`, `details.family`) to `OllamaModel[]`. Returns `[]` on any error. +- `getRecommendedModel(models, systemRamBytes)`: Reads the static catalog, computes usable RAM as 75% of total, ranks catalog variants by quality tier (best > reasoning > balanced > fast), and marks the single best-fitting model as `recommended: true` with a human-readable `recommendationReason`. +- Respects `process.env.OLLAMA_BASE_URL` override — never hard-codes `localhost:11434`. + +### Model Catalog (`server/src/data/ollama-model-catalog.json`) + +5 families with 11 total variants: +- **qwen2**: qwen2.5-coder 7b/14b/32b +- **llama**: llama3.2 3b, llama3.1 8b/70b +- **mistral**: mistral 7b/22b +- **phi**: phi4 14b +- **deepseek**: deepseek-r1 7b/32b + +### Ollama Routes (`server/src/routes/ollama.ts`) + +- `GET /companies/:companyId/ollama/status` — returns `OllamaStatus` JSON +- `GET /companies/:companyId/ollama/models` — returns `{ models: OllamaModel[], ramGb: number }`. Short-circuits to `{ models: [], ramGb: 0 }` if Ollama not installed. +- Both routes gated with `assertCompanyAccess(req, companyId)`. +- Mounted in `app.ts` as `api.use(ollamaRoutes())` after `agentRoutes`. + +## Test Coverage + +12 unit tests (all passing): +- `detectOllama`: success, ECONNREFUSED failure, AbortController timeout, non-ok response +- `listOllamaModels`: success with full OllamaTagsResponse shape, ECONNREFUSED, non-ok +- `getRecommendedModel`: 8GB → 7b, 32GB → 32b, unknown models → all false, empty input, RAM too low → no recommendation + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] server/src/data/ gitignored by root .gitignore** +- **Found during:** Task 1 commit +- **Issue:** Root `.gitignore` has `data/` pattern; `server/src/data/ollama-model-catalog.json` was silently ignored +- **Fix:** Used `git add -f` to force-track the file. The catalog is source code (not generated data), so this is correct behavior. +- **Files modified:** `.gitignore` not modified — file force-added +- **Commit:** 4fce48e1 + +## Known Stubs + +None — all functions return real data structures. Routes wire directly to service functions. No placeholder values in the response paths. + +## Self-Check: PASSED + +Files exist: +- server/src/services/ollama.ts: FOUND +- server/src/data/ollama-model-catalog.json: FOUND +- server/src/__tests__/ollama-service.test.ts: FOUND +- server/src/routes/ollama.ts: FOUND + +Commits exist: +- 2169a21e: FOUND (test RED) +- 4fce48e1: FOUND (feat GREEN + catalog) +- e45a2578: FOUND (feat routes) diff --git a/.planning/phases/28-ollama-integration/28-02-PLAN.md b/.planning/phases/28-ollama-integration/28-02-PLAN.md new file mode 100644 index 00000000..57d88a73 --- /dev/null +++ b/.planning/phases/28-ollama-integration/28-02-PLAN.md @@ -0,0 +1,198 @@ +--- +phase: 28-ollama-integration +plan: 02 +type: execute +wave: 2 +depends_on: [28-01] +files_modified: + - ui/src/api/ollama.ts + - ui/src/adapters/hermes-local/config-fields.tsx + - ui/src/pages/AgentDetail.tsx +autonomous: true +requirements: [OLLA-02, OLLA-03, OLLA-05, HERM-05] + +must_haves: + truths: + - "When Ollama is installed, the Hermes agent config shows a model dropdown listing all locally pulled models" + - "When an Ollama model is selected, adapterConfig saves model + provider:custom + base_url:http://localhost:11434/v1" + - "When Ollama is absent, the config shows an install callout with a link to https://ollama.com/download" + - "Recommended models are visually highlighted in the dropdown" + - "Hermes native skills show an 'Hermes skill' badge in the Skills tab" + - "Manual model entry still works as fallback when Ollama is absent" + artifacts: + - path: "ui/src/api/ollama.ts" + provides: "API client for Ollama status and model listing" + exports: ["ollamaApi"] + - path: "ui/src/adapters/hermes-local/config-fields.tsx" + provides: "Ollama model dropdown, install callout, manual fallback" + contains: "ollamaApi" + - path: "ui/src/pages/AgentDetail.tsx" + provides: "Hermes skill badge in AgentSkillsTab" + contains: "Hermes skill" + key_links: + - from: "ui/src/adapters/hermes-local/config-fields.tsx" + to: "ui/src/api/ollama.ts" + via: "useQuery + ollamaApi.status / ollamaApi.models" + pattern: "ollamaApi" + - from: "ui/src/adapters/hermes-local/config-fields.tsx" + to: "server/src/routes/ollama.ts" + via: "fetch /companies/:companyId/ollama/*" + pattern: "ollama" +--- + + +Create the UI surface for Ollama model selection in Hermes agent config and improve Hermes skill visibility. + +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. + + + +@$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/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 `` with a hybrid control: + - **When ollamaStatus?.installed AND ollamaModels?.models.length > 0**: Render a `