docs(27-hermes-adapter): create phase plan
This commit is contained in:
parent
6328c4a5f9
commit
876665e7a4
2 changed files with 262 additions and 2 deletions
|
|
@ -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 | - |
|
||||
|
|
|
|||
258
.planning/phases/27-hermes-adapter/27-01-PLAN.md
Normal file
258
.planning/phases/27-hermes-adapter/27-01-PLAN.md
Normal file
|
|
@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/27-hermes-adapter/27-RESEARCH.md
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs -->
|
||||
|
||||
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<string, unknown>): { sessionId: string }
|
||||
sessionCodec.serialize(params: { sessionId: string }): { sessionId: string }
|
||||
sessionCodec.getDisplayId(serialized: Record<string, unknown> | 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
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Fix heartbeat sessioned adapters and deduplicate constants</name>
|
||||
<files>server/src/services/heartbeat.ts, packages/shared/src/constants.ts</files>
|
||||
<read_first>
|
||||
- server/src/services/heartbeat.ts (lines 70-80 — SESSIONED_LOCAL_ADAPTERS set)
|
||||
- packages/shared/src/constants.ts (lines 24-36 — AGENT_ADAPTER_TYPES array)
|
||||
</read_first>
|
||||
<action>
|
||||
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)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 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
|
||||
</acceptance_criteria>
|
||||
<done>hermes_local is in SESSIONED_LOCAL_ADAPTERS; AGENT_ADAPTER_TYPES has no duplicate gemini_local; TypeScript compiles cleanly</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Fix create-mode toolsets field in HermesLocalConfigFields</name>
|
||||
<files>ui/src/adapters/hermes-local/config-fields.tsx</files>
|
||||
<read_first>
|
||||
- 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)
|
||||
</read_first>
|
||||
<action>
|
||||
In `ui/src/adapters/hermes-local/config-fields.tsx`, wrap the Toolsets field (the `<Field label="Toolsets" ...>` 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 `<Field>` block (lines 70-89) to be inside the `{!isCreate && (<> ... </>)}` block that starts at line 90. The final structure should be:
|
||||
```
|
||||
{!isCreate && (
|
||||
<>
|
||||
<Field label="Toolsets" ...> ... </Field>
|
||||
<Field label="Persist session" ...> ... </Field>
|
||||
<Field label="Timeout" ...> ... </Field>
|
||||
</>
|
||||
)}
|
||||
```
|
||||
|
||||
Remove the create-mode branch from the Toolsets DraftInput value/onCommit props since the field will only render in edit mode now.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 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
|
||||
</acceptance_criteria>
|
||||
<done>Toolsets field only renders in edit mode; create-mode agents get default "all" toolsets; no extraArgs corruption; TypeScript compiles</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Add hermes session codec test</name>
|
||||
<files>server/src/__tests__/adapter-session-codecs.test.ts</files>
|
||||
<read_first>
|
||||
- server/src/__tests__/adapter-session-codecs.test.ts (full file — existing codec tests as pattern)
|
||||
</read_first>
|
||||
<action>
|
||||
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`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /opt/nexus && pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 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
|
||||
</acceptance_criteria>
|
||||
<done>Hermes session codec has round-trip tests covering serialize, deserialize, getDisplayId, and legacy key variant; all adapter-session-codecs tests pass</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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
|
||||
```
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/27-hermes-adapter/27-01-SUMMARY.md`
|
||||
</output>
|
||||
Loading…
Add table
Reference in a new issue