Skip to content

feat: add Hermes Agent support (export + live SQLite) - #32

Merged
es617 merged 3 commits into
es617:mainfrom
ebolamerican:feat/hermes-support
Aug 29, 2026
Merged

es617 merged 3 commits into
es617:mainfrom
ebolamerican:feat/hermes-support

Conversation

@ebolamerican

Copy link
Copy Markdown
Contributor

Claude Replay already handles Claude Code / Cursor / Codex / Gemini / OpenCode / Kimi Code.

This PR adds Hermes Agent as a first-class source so the same viewer can replay Hermes transcripts without an intermediate conversion step.

What changed

Parser: src/formats/hermes.mjs + src/formats/index.mjs

  • Parses the Hermes raw export (hermes sessions export --format jsonl) — the JSON envelope { id, title, source, messages: [...] } where messages use the SQLite row shape (role/content/tool_calls/tool_call_id/timestamp/reasoning_content/compacted). The trace export (--format trace) already worked via the claude-code parser; this adds the native raw-export path.
  • tool_calls on assistant rows is a JSON array of { id, call_id, function:{name,arguments} }; tool results live in separate role:"tool" rows matched by tool_call_id. The parser correlates them (handling both id and call_id keys) and unwraps the standard {output} envelope so the renderer sees shell/file text rather than a JSON wrapper.
  • Tool name normalization (terminal→Bash, read_file→Read, patch→Edit, write_file→Write, etc.) so the renderer's scene detection (shell output, diffs) triggers.
  • Turn grouping: Hermes records one row per user message with N following assistant/tool rows; the parser merges all consecutive assistant rows into the same user turn so fragmented tool chains render as a single turn.

Live SQLite: src/hermes-db.mjs + src/parser.mjs

  • Reads directly from ~/.hermes/state.db and ~/.hermes/profiles/*/state.db using the Node 22.5+ built-in node:sqlite (DatabaseSync) — no native dependency, graceful fallback on older Node.
  • Virtual path convention ~/.hermes/state.db#session:<id> flows through parser.mjs (parseTranscript / detectFormat) so the CLI and editor can operate on a live DB without an export step.

ID resolution: src/resolve-session.mjs

  • resolveSessionId("20260819_142844_43efe7") now also searches Hermes SQLite (exact + prefix match). Example: claude-replay 20260819_142844_43efe7 -o out.html finds the Hermes session and prints Found: Hermes / codex → …state.db#session:….

CLI: bin/claude-replay.mjs

  • Virtual paths accepted as direct inputs (claude-replay ~/.hermes/state.db#session:ID).
  • Assistant label Sandi for hermes format.
  • Title derived from the Hermes session's title field when available (otherwise falls back to directory basename, matching existing behaviour for export files).

Editor: src/editor-server.mjs

  • New Hermes discovery group (default + per-profile) alongside Claude Code / Cursor / Codex / Kimi. /api/sessions now lists Hermes sessions; /api/search handles virtual paths via SQLite reads.

How to try it

# From an export file
hermes sessions export --session-id <id> --format jsonl /tmp/hermes.jsonl
claude-replay /tmp/hermes.jsonl -o replay.html

# Or by session ID / virtual path (no export needed)
claude-replay 20260819_142844_43efe7 -o replay.html
claude-replay ~/.hermes/profiles/codex/state.db#session:20260819_142844_43efe7 -o replay.html

# Editor (Hermes appears as its own group)
claude-replay

Tests

  • New test/fixture-hermes.json + test/test-hermes.mjs (detect, turn count, tool round-trip, thinking blocks).
  • All 253 non-editor tests pass (including the 5 new hermes cases). test-editor-server.mjs has 11 pre-existing failures on this Node version (reproduces on main without this PR) — no new failures introduced.

Notes

  • node:sqlite is only used when available; without it, Hermes paths are simply not offered — existing formats are unaffected.
  • No dependencies added. Follows the CONTRIBUTING.md two-step format pattern (src/formats/<name>.mjs + registry entry) plus the new shared src/hermes-db.mjs for the editor/CLI SQLite plumbing.

@es617

es617 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Thanks; a solid PR.

A few things before merge:

  • existsSync is imported but unused in src/parser.mjs — oxlint --deny-warnings fails on it.

  • --watch crashes on virtual paths: fsWatch throws ENOENT on state.db#session:ID. Watching the underlying .db instead would fix it (and give live-updating replays for free).

  • The title block runs the Hermes extractTitle on every input file regardless of format — extra full read + per-line JSON.parse on the hot path, and any non-Hermes transcript with a title field in some line would silently change titles. Please gate it on detected format === "hermes".

  • readHermesSessionRaw selects explicit columns (reasoning, reasoning_details, …) — if a Hermes version lacks one, prepare() throws and the session silently fails to load even though discovery lists it. SELECT * would be safer given how fast their schema is moving.

  • Default assistant label "Sandi" → should be "Hermes" (matches how other formats use the product name).

  • resolve-session.mjs reimplements DB discovery + its own sqlite loader — please reuse hermes-db.mjs. Also the LIKE prefix match needs ESCAPE: Hermes IDs contain _, which is a wildcard.

  • Please add the hermes fixture to the Turn-contract list in test-parser.mjs, and update the README (formats table + search paths).

  • detectFormat now swallows read errors and returns "unknown" — not needed for this PR, and it makes failures surface inconsistently. Drop the try/catch.

Happy to merge after these.

oh, and the 11 test-editor-server failures aren't Node-related; they happen when the repo is checked out outside $HOME. The tests load fixtures by absolute path and /api/load rejects anything outside the home dir (assertUnderHome), so everything downstream cascades. Nothing to do in this PR; I'll make the tests location-independent separately.

tuo-lei pushed a commit to ebolamerican/vibe-replay that referenced this pull request Aug 23, 2026
…-aware parse

- discover: scan all known Hermes DBs (default + every
  ~/.hermes/profiles/*/state.db), merging with dedup and timestamp
  sort. Respects HERMES_HOME when set. Previously only the default
  DB was read, so sessions in the active codex profile (and any
  named profile) were invisible in the dashboard.

- parser: openAllHermesDbs + hintedDbPath fast-path. Marker paths
  like .../profiles/codex/state.db#session:ID already carry the
  correct DB; use that before scanning all DBs. Handles both the
  discovered-session and --session flows.

- sqlite: new hermesDbPaths() / openAllHermesDbs() helpers; single-DB
  openHermesDb unchanged.

- tool-mapping: add browser_* (navigate/click/type/snapshot/console/
  scroll/press/get_images), cronjob, memory, and codex so the replay
  transform renders them with a first-class name.

Matches claude-replay Hermes support built in parallel:
  es617/claude-replay#32
tuo-lei pushed a commit to ebolamerican/vibe-replay that referenced this pull request Aug 23, 2026
…-aware parse

- discover: scan all known Hermes DBs (default + every
  ~/.hermes/profiles/*/state.db), merging with dedup and timestamp
  sort. Respects HERMES_HOME when set. Previously only the default
  DB was read, so sessions in the active codex profile (and any
  named profile) were invisible in the dashboard.

- parser: openAllHermesDbs + hintedDbPath fast-path. Marker paths
  like .../profiles/codex/state.db#session:ID already carry the
  correct DB; use that before scanning all DBs. Handles both the
  discovered-session and --session flows.

- sqlite: new hermesDbPaths() / openAllHermesDbs() helpers; single-DB
  openHermesDb unchanged.

- tool-mapping: add browser_* (navigate/click/type/snapshot/console/
  scroll/press/get_images), cronjob, memory, and codex so the replay
  transform renders them with a first-class name.

Matches claude-replay Hermes support built in parallel:
  es617/claude-replay#32
josh and others added 2 commits August 28, 2026 20:03
- Parse Hermes raw export (hermes sessions export --format jsonl) —
  the JSON envelope with { id, title, messages: [...] } where messages
  use the SQLite row shape (role/content/tool_calls/tool_call_id).
  Assistant tool_calls are correlated with role:"tool" rows by
  tool_call_id. Tool output envelopes ({output}) are unwrapped.
- Read live sessions from SQLite (node:sqlite, no native deps) at
  ~/.hermes/state.db and ~/.hermes/profiles/*/state.db. Virtual paths
  like ~/.hermes/state.db#session:ID flow through parser,
  resolve-session, and the editor.
- CLI: session IDs now resolve against Hermes SQLite (exact + prefix),
  virtual paths accepted as direct inputs, assistant label "Sandi" for
  hermes turns, and title derived from session title when available.
- Editor discovery: new Hermes group (default + per-profile), search
  handles virtual paths via SQLite reads.
- Tool name normalization (terminal->Bash, patch->Edit, etc.) so the
  renderer picks the right scenes (shell output, diffs).
- Trace export (hermes sessions export --format trace) already worked
  via the claude-code parser; this adds the raw-export path.

Fixes: hermes transcripts now render as replays without requiring an
intermediate trace conversion.

Co-Authored-By: Sandi Hermes <sandi@hermes.local>
- Remove unused existsSync import in parser.mjs (broke oxlint --deny-warnings)
- Watch the underlying state.db for Hermes virtual paths so --watch works
- Only derive titles via the Hermes extractor when the input is Hermes format,
  instead of reading and line-parsing every input on the hot path
- SELECT * from messages so older/newer Hermes schemas still load
- Default Hermes assistant label to "Hermes" instead of "Sandi"
- Reuse hermes-db.mjs in resolve-session.mjs instead of a private sqlite
  loader; escape LIKE wildcards (Hermes IDs contain underscores)
- Drop swallowed read errors in detectFormat and dead code in hermes.mjs
- Add SQLite integration tests (reduced-schema load, virtual-path parse,
  ID resolution incl. wildcard escaping, profile discovery)
- Add hermes fixture to the Turn-contract suite; update README
@es617
es617 force-pushed the feat/hermes-support branch from 9a3aa53 to 422d10f Compare August 29, 2026 00:05
hermes-db.mjs used top-level await for the node:sqlite probe, which the
es2020 browser bundle can't compile, and pulled node:path/node:os that
the website build had no shims for. Probe synchronously via
createRequire instead, stub the extra modules in the browser shim, and
regenerate docs/index.html — the online demo now detects Hermes exports
and mentions the format alongside the others.
@es617
es617 merged commit 9d5ec64 into es617:main Aug 29, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants