6 phases, 13 plans, 21 requirements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 27-hermes-adapter | 01 | execute | 1 |
|
true |
|
|
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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/27-hermes-adapter/27-RESEARCH.mdFrom server/src/services/heartbeat.ts (line 72-79):
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):
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:
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:
isCreateboolean prop controls create vs edit mode- In create mode:
values!andset!()for CreateConfigValues - In edit mode:
eff()andmark()for adapterConfig fields - Toolsets field (lines 70-89) incorrectly uses
values!.extraArgs/set!({ extraArgs: v })in create mode
- In
packages/shared/src/constants.ts, remove the duplicate"gemini_local"entry fromAGENT_ADAPTER_TYPESarray. 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" <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> hermes_local is in SESSIONED_LOCAL_ADAPTERS; AGENT_ADAPTER_TYPES has no duplicate gemini_local; TypeScript compiles cleanly
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. 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 <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> 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)- Add import at top of file:
import { sessionCodec as hermesSessionCodec } from "hermes-paperclip-adapter/server";
- 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):
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
<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>
Hermes session codec has round-trip tests covering serialize, deserialize, getDisplayId, and legacy key variant; all adapter-session-codecs tests pass
-
Hermes-specific tests pass:
pnpm --filter server exec vitest run src/__tests__/adapter-session-codecs.test.ts src/__tests__/hermes-dual-source.test.ts -
TypeScript compiles for both server and UI:
pnpm --filter server exec tsc --noEmit && pnpm --filter ui exec tsc --noEmit -
Full test suite (sampling):
pnpm --filter server exec vitest run
<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>