feat(hermes): Hermes-only mode — run with no Open WebUI server#522
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
42aac43 to
9695628
Compare
6493ded to
1e4713f
Compare
9695628 to
821a735
Compare
There was a problem hiding this comment.
🟠 High
regenerateMessage passes the isSendBlocked guard for Hermes models with api == null, but then unconditionally calls api!.sendMessageSession(...) at line 4215. This throws a null assertion error in Hermes-only mode because the function lacks the Hermes dispatch path that _sendMessageInternal uses. Consider adding Hermes transport routing before the OpenWebUI API call.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @lib/features/chat/providers/chat_providers.dart around line 4215:
`regenerateMessage` passes the `isSendBlocked` guard for Hermes models with `api == null`, but then unconditionally calls `api!.sendMessageSession(...)` at line 4215. This throws a null assertion error in Hermes-only mode because the function lacks the Hermes dispatch path that `_sendMessageInternal` uses. Consider adding Hermes transport routing before the OpenWebUI API call.
| if (_hermesAvatarBytesCache != null) return _hermesAvatarBytesCache; | ||
| try { | ||
| final data = await rootBundle.load('assets/icons/hermes_agent.png'); | ||
| return _hermesAvatarBytesCache = data.buffer.asUint8List(); |
There was a problem hiding this comment.
🟢 Low widgets/sidebar_user_pill.dart:37
The data.buffer.asUint8List() call ignores the ByteData offset and length, so if the underlying buffer is larger than the asset, the returned Uint8List includes extra trailing bytes that corrupt the cached avatar image. Consider passing data.offsetInBytes and data.lengthInBytes to asUint8List to return only the exact asset bytes.
| return _hermesAvatarBytesCache = data.buffer.asUint8List(); | |
| return _hermesAvatarBytesCache = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @lib/features/navigation/widgets/sidebar_user_pill.dart around line 37:
The `data.buffer.asUint8List()` call ignores the `ByteData` offset and length, so if the underlying buffer is larger than the asset, the returned `Uint8List` includes extra trailing bytes that corrupt the cached avatar image. Consider passing `data.offsetInBytes` and `data.lengthInBytes` to `asUint8List` to return only the exact asset bytes.
821a735 to
37dfc29
Compare
Make Open WebUI optional so a user can install the app and use a self-hosted Hermes Agent exclusively. The mode is derived from what's configured and is switchable; first run shows a backend chooser. - Mode signals: a synchronous persisted `preferredBackend` (unset|owui|hermes) for boot-deterministic routing, plus a derived `hermesOnlyMode` for UI gating (mirrors the reviewer-mode precedent). - Router: Hermes-only short-circuits to chat with no OWUI server/auth; first run routes to a new backend chooser; holds splash while Hermes secrets load. - Onboarding: BackendChooserPage + HermesSettingsPage(isOnboarding) with a Finish button that sets preferredBackend=hermes and enters the app. - Models/defaultModel surface + auto-select the synthetic Hermes model when there's no OWUI API; send guard relaxed for Hermes models. - UI gating: hide chats/notes/terminal/channels (3 synced sites); Hermes is the home tab. preferredBackend=owui set on OWUI connect. Verified end-to-end on an erased iOS simulator (fresh install): chooser → Hermes setup → Finish → Hermes-only chat → only the Hermes tab → relaunch boots straight to chat → send creates a server session with no OWUI. flutter analyze clean; flutter test (2200) passing.
…, tests Address the Hermes-only follow-ups: - Composer: hide OWUI affordances (the "+" overflow button, quick pills, and the iOS keyboard-accessory actions) when a Hermes model is selected — Hermes has no OWUI tools/web-search/image-gen/attachments and uses its own `/` skills. - iOS native settings sheet: gate the OWUI account sections (profile header, AI memory, data connection, password + profile detail sheets, sign-out) on Hermes-only, and make the About detail skip the server lookup so it no longer errors with no server. - Bidirectional switching: add a "Connect to Open WebUI" entry in both the native sheet (routed via a control event in main.dart) and the Flutter profile page; let the router reach the OWUI connect/auth routes for a Hermes-only user; reset preferredBackend to unset when a Hermes-only backend is disabled. - Tests: preferredBackend parse/persistence round-trip and hermesOnlyMode derivation (usable/no-server, server-present, disabled, incomplete, reviewer precedence). flutter analyze clean; flutter test (2211) passing.
…tests
Close out the last Hermes-only follow-ups:
- iOS native settings sheet: render a Hermes-branded profile header in
Hermes-only mode ("Hermes Agent" + the agent host + the hermes_agent.png
avatar). Fully data-driven from Dart — no Swift change; the avatar bytes are
pre-loaded (cached) before the sync config builder runs.
- Extract the inline send/regenerate guard into a pure isSendBlocked() helper
used at both sites, removing the duplicated condition and making the
Hermes-only relaxation unit-testable.
- Tests: isSendBlocked (null model, OWUI-no-api, api present, reviewer, Hermes
relaxation); modelsProvider surfaces only the synthetic Hermes model when
unauthed + usable Hermes; defaultModelProvider auto-selects + writes through
the Hermes model when api == null.
flutter analyze clean; flutter test (2218) passing.
37dfc29 to
b85676e
Compare
| await notifier.ensureSessionKey(); | ||
| await ref |
There was a problem hiding this comment.
🟢 Low views/hermes_settings_page.dart:48
After the two await calls (setEnabled and ensureSessionKey), ref.read(preferredBackendProvider.notifier) on line 49 accesses ref on a potentially disposed ConsumerState, which throws. The if (!mounted) return check on line 52 only guards context.go but comes after the unsafe ref.read. Add a mounted check before line 49 to guard that access as well.
await notifier.ensureSessionKey();
+ if (!mounted) return;
await ref🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @lib/features/hermes/views/hermes_settings_page.dart around lines 48-49:
After the two `await` calls (`setEnabled` and `ensureSessionKey`), `ref.read(preferredBackendProvider.notifier)` on line 49 accesses `ref` on a potentially disposed `ConsumerState`, which throws. The `if (!mounted) return` check on line 52 only guards `context.go` but comes after the unsafe `ref.read`. Add a `mounted` check before line 49 to guard that access as well.
Summary
Makes Open WebUI optional so a user can install the app and use a self-hosted Hermes Agent exclusively — no OWUI server at all. The mode is derived from what's configured and is switchable anytime; first run shows a backend chooser.
Stacked on
feat/hermes-agent(#521) — review that one first; this PR's diff is the Hermes-only delta.Design — two signals
preferredBackendProvider(lib/core/providers/backend_mode_providers.dart) — a synchronous, persistedNotifier<PreferredBackend>(unset | owui | hermes). Read by the router only, for boot determinism — mirrorsreviewerModeProvider's synchronous role. On cold boot the router runsredirect()whileactiveServerProvideris still loading and Hermes secrets are still loading, so a purely-derived gate would bounce a Hermes-only user to server-connection. The flag avoids that; real gating stays derived.hermesOnlyModeProvider(derived, inhermes_providers.dart) —!reviewerMode && hermesConfig.isUsable && activeServer == null. Drives UI gating; widgets rebuild as it settles.What's included
BackendChooserPage(Connect to Open WebUI / Use a Hermes Agent) +Routes.backendChooser. ReusesHermesSettingsPage(isOnboarding: true)with a "Finish setup" button that validatesisUsable, setspreferredBackend = hermes, and enters the app.chatbefore any OWUI server/auth branch; holdssplash(not server-connection) while Hermes secrets load; first run → chooser. Disabled-Hermes safety net avoids a stuck splash.Models.build/defaultModelProvidersurface + auto-select the synthetic Hermes model when there's no OWUI API. Send guard relaxed (extracted to a pureisSendBlocked()helper) to allowapi == nullfor Hermes models.preferredBackend = owuiis set on successful OWUI connect./skills).hermes_agent.pngavatar); the About detail skips the server lookup so it no longer errors with no server.main.dart) and the Flutter profile page; the router reaches the OWUI connect/auth routes for a Hermes-only user;preferredBackendresets tounsetwhen a Hermes-only backend is disabled.Verification
flutter analyzeclean;flutter test(2218) passing — incl. unit tests forpreferredBackendparse/persistence,hermesOnlyModederivation (usable/no-server, server-present, disabled, incomplete, reviewer precedence),isSendBlocked(the send-guard relaxation), andmodelsProvider/defaultModelProviderHermes surfacing + auto-select whenapi == null.Note
Add Hermes-only mode to run without an Open WebUI server
preferredBackendProviderthat persists whether the user has chosen Open WebUI or a Hermes Agent as their backend, read synchronously at boot./backend-chooseronboarding screen that lets first-time users pick between the two backends, replacing the previous direct redirect to/server-connection./chat; the sidebar hides Chats, Notes, Terminal, and Channels tabs; and the profile sheet replaces account options with a 'Connect to Open WebUI' entry.modelsProvideranddefaultModelProvidernow surface and auto-select a synthetic Hermes model when no OpenWebUI API is available, and message send/regenerate are unblocked for Hermes models without an API.GoRouterstate.extra) that auto-enables Hermes, hides the toggle, and routes to chat on completion./backend-chooserinstead of/server-connection.📊 Macroscope summarized 821a735. 17 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.