From 876665e7a4b8c72ec4f1fcf793a3842eab35b64f Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Thu, 2 Apr 2026 16:19:47 +0000 Subject: [PATCH] docs(27-hermes-adapter): create phase plan --- .planning/ROADMAP.md | 6 +- .../phases/27-hermes-adapter/27-01-PLAN.md | 258 ++++++++++++++++++ 2 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/27-hermes-adapter/27-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index d17c1dd5..ebc2fb06 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -27,7 +27,9 @@ 2. When creating a Hermes agent, the user can pick a model and toggle tool permissions; the agent is saved and appears in the agent list 3. When a heartbeat fires for a Hermes agent, Nexus spawns `hermes chat -q` with the task, the process completes, and the result is written back as a task update 4. A second heartbeat on the same Hermes agent resumes the prior session via `--resume`; context from the previous run is accessible -**Plans**: TBD +**Plans:** 1 plan +Plans: +- [ ] 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 @@ -86,6 +88,6 @@ All 16 v1 requirements are mapped to exactly one phase. No orphans. | Phase | Milestone | Plans Complete | Status | Completed | |-------|-----------|----------------|--------|-----------| -| 27. Hermes Adapter | v1.4 | 0/? | Not started | - | +| 27. Hermes Adapter | v1.4 | 0/1 | Not started | - | | 28. Ollama Integration & Agent Surface | v1.4 | 0/? | Not started | - | | 29. Default Provider & End-to-End | v1.4 | 0/? | Not started | - | 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` +