Skip to content

Align chat reconciliation with Hermes Agent dashboard#665

Merged
fathah merged 4 commits into
fathah:mainfrom
pmos69:codex/chat-reconciliation-stabilization
Jun 14, 2026
Merged

Align chat reconciliation with Hermes Agent dashboard#665
fathah merged 4 commits into
fathah:mainfrom
pmos69:codex/chat-reconciliation-stabilization

Conversation

@pmos69

@pmos69 pmos69 commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR reworks Hermes One's chat/session reconciliation path to align much more closely with the direction taken by hermes-agent and the Hermes Agent desktop app: use the dashboard transport and event stream as the primary source of truth, then adapt those events into Hermes One's richer chat UI instead of trying to repeatedly merge partially-overlapping local DB state with renderer-side streaming state.

The main outcome is a more deterministic chat transcript pipeline for local, remote HTTP, and SSH-tunneled Hermes Agent sessions, including streamed reasoning, intermediate assistant output, tool calls, tool results, errors, generated media, pasted attachments, and restored sessions.

Rationale

Hermes One's previous reconciliation strategy tried to merge data modified by the desktop app with data modified independently by hermes-agent. That worked for simple turns, but it was fragile around edge cases:

  • provider/model failures followed by successful provider/model turns in the same session
  • multiple failed/non-failed/failed/non-failed turn sequences
  • tool calls and tool results arriving in different phases from final DB state
  • reasoning/thought blocks interleaved with assistant text and tool activity
  • generated media represented both as markdown images and file paths
  • pasted attachments in live sessions versus restored sessions
  • remote and SSH sessions whose source of truth is not the local Hermes One DB

The Hermes Agent desktop app avoids much of this class of bug by driving the UI from the Hermes Agent dashboard stream/session APIs. This PR follows that same direction while preserving Hermes One-specific functionality, including richer grouping, restored-session rendering, local compatibility paths, remote connection modes, and media display.

In short: Hermes One should not keep inventing a parallel reconciliation model when upstream Hermes Agent is converging on dashboard APIs and event streams. This PR moves Hermes One closer to that upstream-supported flow without dropping existing desktop features.

Major Changes

Dashboard chat transport and event adaptation

  • Adds a dashboard gateway/client path for chat streaming.
  • Adapts Hermes Agent dashboard events into Hermes One chat messages.
  • Preserves ordering of reasoning, assistant text, tool calls, tool results, errors, and final output.
  • Keeps IPC/legacy paths available where needed, but prefers dashboard transport for aligned local/remote/SSH flows.
  • Adds compatibility helpers for current Hermes Agent dashboard/API differences.

Session restoration and continuation

  • Adds remote session APIs and session-history mapping for dashboard-backed sessions.
  • Preserves failed and successful semi-sessions inside the same conversation rather than splitting or dropping error turns.
  • Records continuation metadata so restored sessions can continue without duplicating prior prompts.
  • Improves local/remote/SSH session cache synchronization and refresh behavior.
  • Handles pasted attachments and media paths consistently in restored sessions.

Remote HTTP and SSH dashboard modes

  • Adds remote metadata/session/model support.
  • Adds SSH dashboard tunneling while keeping legacy SSH fallback behavior available.
  • Adds remote/SSH settings controls for dashboard/legacy/auto behavior and active-mode visibility.
  • Ensures session/model lists are refreshed when switching connection modes.
  • Aligns remote and SSH model CRUD with the configured models on the connected Hermes Agent side rather than the local catalog.

Model configuration behavior

  • Uses configured model views for remote/SSH instead of showing the full Hermes Agent model catalog.
  • Persists add/remove model operations to the currently connected Hermes Agent side.
  • Keeps chat model selector in sync after local/remote/SSH mode changes.
  • Adds coverage for authenticated/OAuth provider status paths introduced in current main.

Media and attachment handling

  • Restores media rendering for generated file paths.
  • Shows pasted images in live and restored sessions for local, remote, and SSH flows.
  • Deduplicates repeated image references when a response includes both a markdown image and the same file path.
  • Keeps path text visible while avoiding duplicate rendered images.

Test and lab infrastructure

  • Adds reusable sandbox launcher for isolated Hermes One development.
  • Adds remote HTTP and SSH Docker/WSL lab scripts.
  • Adds a reusable CDP/Playwright live visual regression suite covering local, remote, and SSH.
  • Documents the reconciliation plan, regression playbook, and remote lab setup.

Validation

Automated checks:

  • npm run typecheck passed
  • npm test passed
    • 125 test files passed
    • 1368 tests passed
    • 9 skipped

Live visual regression, driven through the Electron app via CDP/Playwright:

  • local/remote/SSH, generated-image test skipped: 15/15 passed
    • normal prompt with valid model and session restore
    • bad model -> good model -> bad model -> good model in one session and restore
    • add/remove models and verify persistence plus chat selector availability
    • pasted image display in live prompt and restored session
    • same image markdown plus file path renders once
  • local with generated image included: 6/6 passed
    • includes generated image display and restore

Representative reports from the validation run:

  • .sandbox/live-visual-regression/20260613222956/report.json
  • .sandbox/live-visual-regression/20260613224308/report.json

Notes

  • The PR intentionally keeps fallback/legacy paths because not every connection mode or older Hermes Agent installation will expose the full dashboard surface.
  • Remote/SSH dashboard behavior is modeled as dashboard-over-HTTP, with SSH implemented as a tunnel to that dashboard rather than a separate renderer protocol.
  • The implementation is intentionally conservative about Hermes Agent ownership: model/session changes in remote or SSH mode are applied to the connected Hermes Agent side, not to the local Hermes One sandbox.

@pmos69 pmos69 force-pushed the codex/chat-reconciliation-stabilization branch 3 times, most recently from aa39d6c to 4ffb795 Compare June 13, 2026 23:24
@pmos69

pmos69 commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR reworks the chat/session reconciliation path to use the Hermes Agent dashboard WebSocket as the primary event source, replacing the previous approach of merging partially-overlapping local DB state with renderer-side streaming state. The change covers local, remote HTTP, and SSH-tunneled sessions with consistent handling of reasoning, tool calls, errors, generated media, and restored sessions.

  • Dashboard transport: New useDashboardChatTransport hook and DashboardGatewayClient implement a JSON-RPC WebSocket session lifecycle (create/resume, model switching, attachment sync, clarify flow) with generation-based guards against stale connections on mode changes; dashboardEventAdapter.ts maps raw dashboard stream events to ChatMessage with deduplication.
  • Remote/SSH model and session bridging: remote-sessions.ts, remote-models.ts, and remote-metadata.ts expose full CRUD over the dashboard HTTP API; withRemoteDashboard / withSshDashboardSessions wrappers provide auto/dashboard/legacy transport fallback throughout the IPC layer.
  • Session continuation and local error overlays: session-continuation-store.ts records prior-session context and per-turn error records in SQLite, prepended as synthetic history items by applySessionLocalOverlays so restored sessions carry full transcript context; db.ts introduces a shared cached connection replacing per-operation open/close.

Confidence Score: 4/5

Safe to merge for the core dashboard transport path; the SSH tunnel and remote model helpers have minor robustness gaps worth cleaning up in a follow-on.

The PR is large (103 files, 1368 tests passing) and addresses the main fragility points raised in earlier review rounds — atomic compat patching, session_id validation, and text-based failure heuristics are all improved. The remaining issues are timing edge cases: the SSH tunnel marks itself running before health is confirmed when the port-open deadline expires quietly, and the remote model helpers incorrectly interpret HTTP 204 responses as failures. Neither breaks the happy path, but the SSH one could cause confusing multi-second delays in failure scenarios.

src/main/ssh-tunnel.ts (tunnelRunning set before health check when port-open deadline expires), src/main/remote-models.ts (remoteRemoveModel / remoteUpdateModel interpret null 204 bodies as failures), src/renderer/src/screens/Chat/dashboardGatewayClient.ts (spurious onClose after failed connections)

Important Files Changed

Filename Overview
src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts New 1331-line hook implementing dashboard-based chat transport with session lifecycle management, attachment sync, provider resolution, and failure handling; previous-thread issues (reasoningSegmentClosed ref outside updater, session_id validation, text heuristics) are addressed
src/renderer/src/screens/Chat/dashboardEventAdapter.ts New 617-line adapter converting Hermes Agent dashboard WebSocket events to ChatMessage arrays; handles reasoning, assistant text, tool calls/results, clarify flows, and completion failures with well-scoped regex guards
src/renderer/src/screens/Chat/dashboardGatewayClient.ts New WebSocket JSON-RPC client for the dashboard; close event listener retained after a failed connection causes options.onClose to fire for connections that never opened — harmless in current usage but a latent footgun for future handlers
src/main/dashboard.ts New 639-line dashboard lifecycle manager covering local spawn, remote HTTP, and SSH tunnel modes; includes authentication probing, health-wait loops, and OAuth detection
src/main/ssh-tunnel.ts SSH tunnel refactored to add in-flight deduplication, SSH binary existence check, and dual health path; when port-open polling deadline expires without error, tunnelRunning = true is set prematurely and an unnecessary 20s waitForHealth window follows before failure is surfaced
src/main/hermes-agent-compat.ts New 621-line compat patcher injecting model-library and embedded-chat endpoints into web_server.py; now uses writeCompatFileAtomically (write-to-tmp + rename) and .orig backup, addressing the atomicity issue raised in the previous review thread
src/main/remote-sessions.ts New 563-line remote session bridge implementing full CRUD over HTTP; remoteRequestJson resolves to null on empty bodies, meaning callers checking response.ok will silently return false for HTTP 204 responses
src/main/remote-models.ts New 314-line remote model library bridge; remoteRemoveModel and remoteUpdateModel check response.ok === true which evaluates to false for HTTP 204 No Content responses, incorrectly reporting failure for successful server-side operations
src/main/session-continuation-store.ts New 362-line store recording session continuation items and local error overlays in SQLite using synthetic IDs and timestamps that correctly sort before real session history; DB migration is idempotent via CREATE TABLE IF NOT EXISTS
src/main/db.ts New shared cached DB connection module replacing per-operation open/close; automatically reopens when path or readonly mode changes
src/main/index.ts Major IPC expansion adding dashboard lifecycle, remote/SSH session bridging, model CRUD, continuation store persistence, connection-config-changed events, and auto/dashboard/legacy transport selection

Sequence Diagram

sequenceDiagram
    participant R as Renderer (useDashboardChatTransport)
    participant P as Preload (hermesAPI)
    participant M as Main (index.ts / dashboard.ts)
    participant D as Dashboard (Hermes Agent)

    R->>P: startDashboard(profile)
    P->>M: IPC startDashboard
    M->>D: spawn / verify local or remote dashboard
    D-->>M: "DashboardStatus { wsUrl, token }"
    M-->>P: DashboardStatus
    P-->>R: DashboardStatus

    R->>D: WebSocket connect (wsUrl + token)
    R->>D: session.resume OR session.create
    D-->>R: "{ session_id, stored_session_id }"

    R->>D: [optional] slash.exec /model provider
    R->>D: [optional] image.attach_bytes / file.attach
    R->>D: "prompt.submit { session_id, text }"

    loop stream events
        D-->>R: message.delta / reasoning.delta / tool.start / tool.complete
        R->>R: applyDashboardStreamEvent to setMessages
    end

    D-->>R: "message.complete { status, usage }"
    R->>P: recordSessionContinuation (storedSessionId, items)
    P->>M: IPC recordSessionContinuation
    M->>M: persistSessionContinuation (SQLite)
Loading

Reviews (4): Last reviewed commit: "feat: align chat reconciliation with Her..." | Re-trigger Greptile

Comment thread src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts Outdated
Comment thread src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts
Comment thread src/main/hermes-agent-compat.ts
Comment thread src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts
@pmos69 pmos69 force-pushed the codex/chat-reconciliation-stabilization branch from 4ffb795 to 7bba49f Compare June 14, 2026 01:21
@pmos69

pmos69 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread src/renderer/src/screens/Chat/hooks/useDashboardChatTransport.ts
@pmos69 pmos69 force-pushed the codex/chat-reconciliation-stabilization branch from 7bba49f to 5225dd8 Compare June 14, 2026 02:53
@pmos69

pmos69 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread src/main/ssh-tunnel.ts
Comment on lines +200 to +208
export async function startSshTunnel(config: SshConfig): Promise<void> {
if (tunnelStartPromise) return tunnelStartPromise;
tunnelStartPromise = startSshTunnelInner(config);
try {
await tunnelStartPromise;
} finally {
tunnelStartPromise = null;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 When a second caller invokes startSshTunnel with a different config while a start is already in flight, the new config is silently discarded and the caller receives the in-flight promise for the first config. The caller is told "success" but ends up holding a tunnel bound to the wrong host/port/key. A config change should either wait for the current start to finish and then start again, or abort the in-flight start before launching with the new config.

Suggested change
export async function startSshTunnel(config: SshConfig): Promise<void> {
if (tunnelStartPromise) return tunnelStartPromise;
tunnelStartPromise = startSshTunnelInner(config);
try {
await tunnelStartPromise;
} finally {
tunnelStartPromise = null;
}
}
export async function startSshTunnel(config: SshConfig): Promise<void> {
// If an identical config is already starting, coalesce onto its promise.
// If the config differs, wait for the in-flight start to settle first so we
// don't race, then launch a fresh start with the new config.
if (tunnelStartPromise) {
await tunnelStartPromise.catch(() => undefined);
if (
activeConfig?.host === config.host &&
activeConfig?.port === config.port &&
activeConfig?.user === config.user &&
activeConfig?.keyPath === config.keyPath &&
tunnelRunning
) {
return;
}
}
tunnelStartPromise = startSshTunnelInner(config);
try {
await tunnelStartPromise;
} finally {
tunnelStartPromise = null;
}
}

@pmos69

pmos69 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

@greptileai

@fathah fathah merged commit a6a24bd into fathah:main Jun 14, 2026
1 check 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.

3 participants