Commit Graph
10 Commits
Author SHA1 Message Date
M57 065c7fd75f feat(chat): display token usage and tps in message footers (#1094) 2026-09-12 16:04:13 +03:00
M57andM57 4be1e965f2 feat: render display_kind timeline markers as chips, not user bubbles (#904) (#909)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-15 14:06:51 +03:00
M57andM57 adde03a4de fix(chat): stop duplicating interim commentary before tool calls (issue #842) (#854)
* fix(chat): stop duplicating interim commentary before tool calls (issue #842)

Interim commentary the agent narrates before each tool call rendered
TWICE. Two mechanisms, both now covered:

1. The old tool.start orphan seal + message.complete's full text: when the
   complete payload repeats the commentary, the final bubble duplicated
   it. onMessageComplete now strips the sealed-orphan prefix (tracked in
   StreamingState) from the final text.

2. On-device capture + DB forensics (2026-08-08) exposed the real
   repeat offender: the app seals the RAW streamed text — which carries
   leading blank lines the model emits before its narration — while the
   backend persists a CLEANED copy (leading whitespace stripped). A
   transcript reload ~10s later merged the REST page, exact-content
   matching failed, and the cleaned REST copy rendered on top of the
   live bubble. sameLogicalMessage now compares trimmed content, so the
   sealed live bubble is covered by the REST row.

Also:
- session.interrupt seals the in-flight streaming message before
  clearing streaming state, so interrupting a commentary turn keeps the
  partial text
- regression tests cover both turn shapes (commentary in the final row →
  stripped; absent → kept) and the whitespace-drift reload case

* fix(chat): match tool rows by gateway call id, drop reasoning-echo dupes (#842)

REST transcript rows carry tool_call_id; live WS bubbles keep tool_id from
tool.start. Match tool rows on that 1:1 identity so MCP/web rows (stored as
raw <untrusted_tool_result> text with no JSON key) dedupe during the
post-turn transcript merge instead of ghosting after the final message.

reasoning.available echo (narration text == message body, sent ~40ms before
each tool.start) no longer overwrites the real reasoning trace, so narration
bubbles don't render their own text twice (body + Reasoning card).

Room v5 migration adds tool_call_id; regression tests use captured
on-device payload shapes.

* fix(chat): flush throttled tokens before orphan seal + prefix-safe dedupe (#842)

The tool.start seal raced the 33ms throttled token flush: deltas landing just
before the transition stayed in the buffer, so the sealed narration orphan came
out truncated (on-device capture: orphan ended '...chickpea', backend persisted
'...chickpea goodness:') and failed the logical-match against the clean REST
row — the commentary ghosted on the next transcript sync.

- ChatStreamingController.flushPendingTokens(): force-flushes the throttled
  TOKEN buffer on MessageStart/MessageComplete/MessageDone/ToolStart so the
  reducer seals the COMPLETE narration.
- sameLogicalMessage: accept prefix-covering (both sides >=40 chars) so a
  residual seal drift can never re-add a substantial narration.
- Regression tests: controller flush + truncated-orphan prefix match.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-08 23:34:58 +03:00
M57andM57 53b3966931 feat(#525): render reasoning.delta / reasoning.available stream (#543)
* feat(#525): add ReasoningDelta + ReasoningAvailable WsEvent types

* feat(#525): parse reasoning.delta + reasoning.available; route through reducer + StreamingState

* feat(#525): add reasoning streaming buffer to ChatStreamingController

* feat(#525): dispatch ReasoningDelta/ReasoningAvailable in ChatViewModel

* feat(#525): render collapsible ReasoningIndicator in chat list

* fix(#525): address review — icons+semantics, LocalHermesStatusColors, drop redundant ReasoningAvailable branch

* feat(#525): persist reasoningText on model, entity, mapper + Room migration 3->4

* feat(#525): reducer + streaming carry reasoningText onto message

* feat(#525): render persisted reasoning above each assistant bubble, drop global indicator

* nix: flake

* nix: flake

---------

Co-authored-by: M57 <m57@hermes-control.app>
2026-07-10 23:41:53 +03:00
M57 8f14aca647 perf: optimize startup, database, and ui rendering (#405)
* perf: optimize startup, database, and ui rendering

* fix(#405): address code review feedback

* test: wait for async prefs initialization to prevent coroutine leaks

* test: stub mockViewModel.streamingState in ChatScreenTest to prevent ClassCastException on Android
2026-06-27 01:38:44 +03:00
M57andM57 42fcf2a14b fix: implement in-memory token caching to avoid redundant prefs decryptions (#320)
* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.
- Fixed merge conflicts and ensured code still tests well.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.

* fix: implement in-memory token caching to avoid redundant prefs decryptions

Resolves #277

Previously, the `AuthManager.getToken()` method decrypted the entire
`EncryptedSharedPreferences` file on every call. Because this method is
used in the OkHttp interceptor on every HTTP/WS request, and frequently
during navigation, it introduced 10-30ms of redundant work per call.
This compounded to noticeable UI lag (e.g., during chat session switches).

This commit implements thread-safe in-memory caching for the auth token
inside `AuthManager`.
- Added a `@Volatile` `cachedToken` and `tokenInitialized` flag.
- Modified `getToken()` to return the cached value after initial evaluation,
  using double-checked locking (`synchronized(this)`) to avoid race conditions.
- Updated `setToken()` and `setSelectedProfileId()` to correctly invalidate
  or update the cache.
- Added comprehensive unit tests in `AuthManagerTest.kt` to verify caching
  behavior and proper invalidation upon profile switches.
- Fixed GitGuardian warning by removing the "my_secret_token" from the test.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 22:19:35 +03:00
M57andM57 eb71ae3a46 Fix WebSocket events being silently dropped on buffer overflow (#313)
* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow `extraBufferCapacity` from 64 to 256
and changes the `onBufferOverflow` policy to `BufferOverflow.DROP_OLDEST`
in `HermesWsClient.kt`. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting `DROP_OLDEST`, `tryEmit()` remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix ktlint formatting issues in HermesWsClient.kt

The formatting changes adhere to ktlint rules required by the CI pipeline:
- Removed a trailing comma in MutableSharedFlow initialization to avoid multiline expression wrapping errors.
- Sorted imports to be in lexicographical order.

These formatting changes fix the failing CI check.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow `extraBufferCapacity` from 64 to 256
and changes the `onBufferOverflow` policy to `BufferOverflow.DROP_OLDEST`
in `HermesWsClient.kt`. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting `DROP_OLDEST`, `tryEmit()` remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 22:13:55 +03:00
M57andM57 2c6e437a5b Fix: Prevent AuthManager state pollution on connection verification (#348)
* Fix: Prevent AuthManager state pollution on connection verification

Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests.

Fixes #260

* Fix: Prevent AuthManager state pollution on connection verification

Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests.

Fixes #260

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 20:49:58 +03:00
M57 5e93fb2a4e perf: add composite index to fix chat session loading latency (fixes #276) (#316)
* perf: add composite index on session_id and timestamp to chat_messages

Fixes a high-severity performance issue (PERF-08) described in issue #276.
A full table scan and in-memory sort were previously performed during each session-switch or cache-load due to a missing index.
By adding a composite index on `(session_id, timestamp)` in the `ChatMessageEntity` and bumping the database version from 1 to 2 with a migration, the query for loading messages now avoids the full table scan, making chat loading latency independent of total message count.

Changes made:
- Added `indices` to `@Entity` in `ChatMessageEntity.kt` for `session_id` and `timestamp`.
- Bumped `version` to 2 in `HermesDatabase.kt`.
- Added explicit migration `MIGRATION_1_2` to create the index if it doesn't exist and added it to `.addMigrations()`.

Fixes #276

* style: fix ktlint formatting errors in HermesDatabase.kt

Fixed `standard:argument-list-wrapping` and `standard:max-line-length` ktlint errors in `app/src/main/java/com/m57/hermescontrol/data/local/HermesDatabase.kt`.
This ensures that the repository meets standard code style guidelines and fixes the GitHub CI build check failures.
2026-06-23 12:32:48 +03:00
M57 f2e11413cc fix(#66): enable room schema export and configure release migration safe-guards (#177) 2026-06-20 22:57:08 +03:00