feat(app): surface Hermes agent in client (follow-up to #1322) - #1329
feat(app): surface Hermes agent in client (follow-up to #1322)#1329chphch wants to merge 3 commits into
Conversation
Proof — Hermes surfaces in Settings → Agent DefaultsCaptured locally from this branch's worktree ( The animation loops between two states of
Note that this PR does not surface Hermes in the new-session agent picker on its own — that path also needs the daemon to be running with CI (typecheck + happy-app tests) is green from the previous PR commit — this commit only adds app-side surfacing. |
Proof — Hermes also surfaces in the new-session agent picker (with #1322 amendment)Follow-up to the previous proof comment — that one only covered the Settings → Agent Defaults path. While capturing the live picker flow, I discovered a gap: even with the daemon paired and This trace shows the new-session picker cycling through all five agents — Captured locally from Required amendment to #1322The previous comment claimed "#1322 delivers
I'll open a small follow-up PR against the same branch (or amend #1322 if the maintainer prefers) with these three changes. The app-side surfacing in this PR (#1329) is correct as-is — it doesn't depend on detection; once detection lands, Hermes appears in the picker without further app changes. Verification artifacts
|
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
e7e692f to
9866afd
Compare
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
|
Follow-up findings from local testing of the Hermes app surfacing work:
Suggested areas to update:
The Hermes ACP adapter exposes session modes as
Hermes is ACP-based and can report operating modes through metadata. The app should prefer
Hermes behaves more like OpenClaw here: the Happy worktree picker/creation flow is not supported for Hermes sessions. The new session UI should hide/reset worktree selection for Hermes.
The permission style/badge mapping should recognize |
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
9866afd to
f103473
Compare
|
Real-world confirmation of the symptom this PR describes, from an end user rather than a reviewer. I have no competing PR and no stake in the diff. Your PR body says it exactly:
That is precisely what I hit today, and I can add the other half of the evidence: the CLI side already works, so this PR is the only thing standing between users and a working Hermes flow. CLI side: works unmodified, today
26 tools, the App side: not reachable, three attemptsThe agent picker on Android offers exactly four entries: What I had not expected is that there is also no way to reach the already-running CLI-started session from the app. Three attempts, each ended with the daemon spawning a new Claude Code session instead. From The So from a user's point of view the CLI bridge is complete and the app is the entire remaining gap. That is a stronger argument for this PR than "it would be nice to have Hermes in the list." One adjacent gap that this PR does not closeWhile tracing the above I found that Nothing here needs action from you. Posting it so there is user-side evidence attached to the PR, since it has been open since 2026-05-25 with no maintainer comment and the CLI half (#1322) is |
Adds first-class support for NousResearch/hermes-agent via the Agent Client Protocol, mirroring the pattern in slopus#1276 (Kimi CLI). Hermes ships ACP out of the box through `hermes acp` (installed via `pip install 'hermes-agent[acp]'`). Closes slopus#1129. - New factory `agent/factories/hermes.ts` (+ unit tests) builds an AcpBackend with `command='hermes'`, `args=['acp']`. - New `hermes/runHermes.ts` thin wrapper around the generic ACP runner, with a `hermesCliAvailable()` pre-flight that exits with a `pip install` hint when `hermes` is not on PATH. - The pre-check fires in the `happy hermes` subcommand BEFORE auth/daemon setup, so first-time users without Hermes installed are not dragged through the QR flow only to hit an install error. - Wires `hermes` into KNOWN_ACP_AGENTS, resolveSessionFlavor, AgentId, BackendFlavor, and the factories barrel. - README + `--help` list `happy hermes` alongside the other agents. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The previous commit added the Hermes ACP factory and wiring but missed the discovery side, so the daemon never advertised `cliAvailability.hermes` to the server. The app-side picker in slopus#1329 filters agents by `cliAvailability[a.key] === true`, which silently dropped Hermes whenever a machine was selected. - detectCLI.ts: add `hermes: boolean` to `CLIAvailability`, detect via `commandExists('hermes')` (POSIX) and `checkCommand('hermes')` (Windows). - api/types.ts: add `hermes: z.boolean().optional()` to `MachineMetadataSchema.cliAvailability` — optional so older CLIs without this patch still validate. - api/apiMachine.ts: include `prev.hermes !== newAvailability.hermes` in the `cliAvailabilityChanged` check so the re-detect loop pushes the change when `hermes` is installed mid-session. Verified end-to-end with slopus#1329 stacked on top: live daemon + Expo web, new-session agent picker cycles claude code → codex → openclaw → gemini → hermes. Proof GIF posted on slopus#1329. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
PR slopus#1322 wires Hermes Agent into the CLI (mirrors the Kimi/slopus#1276 self-hosted-CLI pattern). On its own that exposes no entry point in the mobile/web app: the agent picker, icons, flavor labels, defaults and permission/model option pickers are all hardcoded to the prior agent set, so a CLI with hermes available is invisible to the user. This change threads `'hermes'` through every app-side type/list: - Agent picker + icon (`new/index.tsx`, dev composer) — new purple "H" monogram placeholder until Nous Research ships an official asset. - Settings → Agent Defaults (`settings/agents.tsx`) — Hermes group with permission/model rows. - Flavor → label resolver (`session/[id]/info.tsx`) → 'Hermes'. - Type unions: `NewSessionAgentType`, `ops.startSession` agent option, `AgentInput` agentType, `modelModeOptions` AgentFlavor, raw session provider enum. - `agentDefaults`: `agentKeys` includes hermes; per-agent override slot added; static `codeAgentDefaults.hermes` set to neutral defaults (model picker is populated at runtime from ACP `available_models`, so the static fallback intentionally stays empty). - `modelModeOptions`: `getHermesPermissionModes` exposes only `default` (Hermes ACP currently doesn't advertise operating modes — `hooks_auto_accept` is CLI-side). `getHermesModelModes` ships a sentinel `default`; the real list comes from `metadata.models` populated by the ACP `available_models` event. - `storageTypes.cliAvailability.hermes`: optional boolean for backward-compat with CLIs that predate Hermes — older daemons keep validating without breakage. - 11 locale tables (`_default.ts` + 10 translations) gain the `agents.hermes = 'Hermes'` row. Verification: - `pnpm -r typecheck`: 6/6 packages clean. - `pnpm --filter happy-app test`: 487 passed, 57 skipped (pre-existing). - Manual smoke: `happy hermes` from worktree boots ACP, server reports 26 OpenRouter models + 9 slash commands; with this PR the app surfaces Hermes in the agent picker and resolves its flavor label correctly. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
f103473 to
9abd36c
Compare
|
Rebased onto current Single conflict, in
|
|
Closing this along with #1322 — I've stopped focusing on Hermes, and this PR in particular shouldn't stay open when there's a substantive review on it I never answered. @openclaw-bc — you left five concrete findings back on 2026-07-01 and I never actioned them. That's on me. Rather than let that work evaporate, here it is as the handover list; I re-checked the branch today and none of the five are addressed, so this is an accurate map of the remaining work:
@hansai-art — thank you for the end-user side (comment); "the CLI already works, so this PR is the only thing between users and a working Hermes flow" is exactly the right framing, and it's still true. Item 1 above is the concrete reason the app half doesn't get there yet. Branch stays on my fork ( Please do pick this up if you want it — reopen under your own name, no permission needed, no attribution expected. The five items above plus a working daemon spawn path is a complete, well-scoped piece of work for whoever wants Hermes in the app. If a maintainer prefers it stay mine, ping me and I'll reopen. |


Summary
happy-clibut does not surface it in the app — the agent picker, icons, flavor labels, defaults and option pickers are all hardcoded to the prior agent set, so a CLI advertisinghermes: trueis invisible to the user.'hermes'through every app-side type/list so the existing CLI bridge becomes user-reachable. Stacked on feat(cli): add Hermes Agent CLI integration via ACP #1322 — it has to be merged after (or with) feat(cli): add Hermes Agent CLI integration via ACP #1322. The diff vsmainof this PR alone is app-only; combined with feat(cli): add Hermes Agent CLI integration via ACP #1322 it gives a complete end-to-end Hermes flow.What changes
app/(app)/new/index.tsx,app/(app)/dev/session-composer.tsx) — newicon-hermes{,@2x,@3x}.pngpurple "H" monogram placeholder until Nous Research ships an official asset.app/(app)/settings/agents.tsx) — Hermes section with permission/model rows.app/(app)/session/[id]/info.tsx) →'Hermes'.NewSessionAgentType,ops.startSessionagent option,AgentInput.agentType,modelModeOptions.AgentFlavor, rawproviderenum insync/typesRaw.ts.sync/agentDefaults:agentKeysincludes'hermes'.AgentDefaultOverridesSchema.hermesslot added (optional, like other agents).codeAgentDefaults.hermesset to neutral defaults — model picker is populated at runtime from ACPavailable_models, so the static fallback intentionally stays empty.components/modelModeOptions:getHermesPermissionModesexposes onlydefault(Hermes ACP currently doesn't advertise operating modes —hooks_auto_acceptis CLI-side).getHermesModelModesships a sentineldefault; the real list comes frommetadata.modelspopulated by the ACPavailable_modelsevent at session start.sync/storageTypes.cliAvailability.hermes:z.boolean().optional()for backward-compat with daemons that predate Hermes._default.ts+ 10 translations: en/zh-Hans/es/ru/pl/zh-Hant/pt/it/ja/ca) gain theagents.hermes = 'Hermes'row.Why split it off #1322
#1322 has been kept tight to match the Kimi/#1276 precedent (CLI-only, no app surface). The Devin PR #1317 took the opposite approach (CLI + app in one PR) and is harder to review. Keeping the app surfacing as its own PR makes #1322 reviewable alongside #1276 and isolates the icon/i18n churn here.
Proof
Hermes surfaced in the app — agent picker + Agent Defaults rows, and the new-session picker:
Test plan
pnpm -r typecheck— 6/6 workspace packages clean.pnpm --filter happy-app test— 487 passed / 57 skipped (no regressions vs main).happy hermesboots ACP, server reports 26 OpenRouter models + 9 slash commands. With this PR applied, the app surfaces Hermes in the agent picker and resolves the flavor label correctly.Notes for reviewers
cliAvailability.hermesis.optional()so older daemons keep validating. No wire-format break.happy-wirepackage — purely app-side.🤖 Generated with Claude Code
via Happy