Commit Graph
15 Commits
Author SHA1 Message Date
m57 d81fc39f25 ci: faster, stricter CI (instrumented gate, parallel jobs, test forks) (#1348) 2026-09-28 00:20:52 +03:00
M57 dc77b306b6 feat: auto-dismiss reply notifications when read in chat (#1195) 2026-09-19 22:58:43 +03:00
M57 2d0b6ae1a9 fix(history): resolve session resume profile scoping and history pagination (#1040) 2026-09-02 00:01:19 +03:00
M57andM57 e854559de4 fix(chat): send omit_messages=true on session.resume (issue #842) (#844)
The gateway replays the full transcript over a new socket on
session.resume unless omit_messages is set; mobile hydrates history via
REST in parallel, so the replay is redundant. When a reconnect lands
mid-stream, the replayed message gets a fresh UUID and dedup can't
collapse the 90% orphan with the 100% replay (different ids + content),
leaving a duplicate bubble. Desktop already sends omit_messages: true;
mirror that in both resume paths (ChatViewModel + NotificationReplyReceiver)
and pin it in the exact-map test verifies.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-07 22:21:16 +03:00
AlexDev_ 356ccc98b5 fix(notification): resume stored session before reply (#805) 2026-08-04 19:54:49 +03:00
M57andM57 83ec590ed8 fix(notifications): only run waiting-reply foreground service while a reply is pending (#795)
The chat notification FGS — and its mandatory persistent 'Waiting for
Hermes replies' notification — was started on every ON_STOP of the chat
screen, even when nothing was in flight, and lingered at app startup.

Track a pending-reply flag on HermesWsClient (set when a prompt is
submitted and on any in-flight turn event, cleared on MessageComplete),
gate NotificationHelper.start() on it, and have the service retire
itself after delivering the reply notification in the background.
Replies sent from the notification re-arm the service for the
follow-up turn.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-04 16:59:42 +03:00
M57 ba2d4cf896 perf(#518): wrap NotificationReplyReceiver work in withContext(Dispatchers.IO) (#518)
Explicit IO context for DB/WS/notify path. replyScope already uses
Dispatchers.IO; this makes the offload local and explicit. Keep return@withContext
only because nothing follows the withContext block today.
2026-07-09 21:18:57 +03:00
M57andM57 3399880621 perf(#281): fix PERF-13/14/15/16 — main-thread settings, 100Hz polling, leaked scope, WS disconnect (#359)
* perf(#281): fix PERF-13/14/15/16 — main-thread settings load, 100Hz polling, leaked scope, WS disconnect

* fix(#281): fix SettingsViewModelTest failures from async init dispatcher

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-25 20:39:52 +03:00
M57andM57 3605ca7537 Security: Validate session ID in NotificationReplyReceiver (#327)
* Security: Validate session ID in NotificationReplyReceiver

- Updated `ChatMessageDao.kt` to include `sessionExists` query.
- Guarded `NotificationReplyReceiver.kt` `HermesWsClient.sendMessage` execution behind the `dao.sessionExists(sessionId)` check, preventing potential intent injection payloads against inactive/unknown sessions.
- Moved message dispatch into DB IO coroutine to ensure safe execution.
- Added tests in `NotificationReplyReceiverTest.kt` to assert unknown sessions ignore replies properly.

Fixes #268

* Security: Validate session ID in NotificationReplyReceiver

- Updated `ChatMessageDao.kt` to include `sessionExists` query.
- Guarded `NotificationReplyReceiver.kt` `HermesWsClient.sendMessage` execution behind the `dao.sessionExists(sessionId)` check, preventing potential intent injection payloads against inactive/unknown sessions.
- Moved message dispatch into DB IO coroutine and wrapped with a 5s timeout to ensure safe execution.
- Added tests in `NotificationReplyReceiverTest.kt` to assert unknown sessions ignore replies properly.

Fixes #268

* Security: Validate session ID in NotificationReplyReceiver

- Updated `ChatMessageDao.kt` to include `sessionExists` query.
- Guarded `NotificationReplyReceiver.kt` `HermesWsClient.sendMessage` execution behind the `dao.sessionExists(sessionId)` check, preventing potential intent injection payloads against inactive/unknown sessions.
- Moved message dispatch into DB IO coroutine and wrapped with a 5s timeout to ensure safe execution.
- Added tests in `NotificationReplyReceiverTest.kt` to assert unknown sessions ignore replies properly.

Fixes #268

* Security: Fix fake token triggering GitGuardian

- Replaced high-entropy string `ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890` with obvious fake string `dummy_token_value_that_is_long_enough_to_pass_validation_123` in `ConnectViewModelTest.kt` to fix a false positive alert during GitHub Actions GitGuardian security check.

Fixes #268 CI Check failure

* Security: Fix fake token triggering GitGuardian and validate session ID

- Updated `ChatMessageDao.kt` to include `sessionExists` query.
- Guarded `NotificationReplyReceiver.kt` `HermesWsClient.sendMessage` execution behind the `dao.sessionExists(sessionId)` check, preventing potential intent injection payloads against inactive/unknown sessions.
- Moved message dispatch into DB IO coroutine and wrapped with a 5s timeout to ensure safe execution.
- Added tests in `NotificationReplyReceiverTest.kt` to assert unknown sessions ignore replies properly.
- Replaced high-entropy string `ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890` with obvious fake string `dummy_token_value_that_is_long_enough_to_pass_validation_123` in `ConnectViewModelTest.kt` to fix a false positive alert during GitHub Actions GitGuardian security check.

Fixes #268

* Security: Fix fake token triggering GitGuardian and validate session ID

- Updated `ChatMessageDao.kt` to include `sessionExists` query.
- Guarded `NotificationReplyReceiver.kt` `HermesWsClient.sendMessage` execution behind the `dao.sessionExists(sessionId)` check, preventing potential intent injection payloads against inactive/unknown sessions.
- Moved message dispatch into DB IO coroutine and wrapped with a 5s timeout to ensure safe execution.
- Added tests in `NotificationReplyReceiverTest.kt` to assert unknown sessions ignore replies properly.
- Replaced high-entropy string `ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890` with obvious fake string `dummy_token_value_that_is_long_enough_to_pass_validation_123` in `ConnectViewModelTest.kt` to fix a false positive alert during GitHub Actions GitGuardian security check.

Fixes #268

* Security: Validate session ID in NotificationReplyReceiver and fix false positive secret

- Updated `ChatMessageDao.kt` to include `sessionExists` query.
- Guarded `NotificationReplyReceiver.kt` `HermesWsClient.sendMessage` execution behind the `dao.sessionExists(sessionId)` check, preventing potential intent injection payloads against inactive/unknown sessions.
- Moved message dispatch into DB IO coroutine and wrapped with a 5s timeout to ensure safe execution.
- Added tests in `NotificationReplyReceiverTest.kt` to assert unknown sessions ignore replies properly.
- Replaced high-entropy string `ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890` with obvious fake string `dummy_token_value_that_is_long_enough_to_pass_validation_123` in `ConnectViewModelTest.kt` to fix a false positive alert during GitHub Actions GitGuardian security check.

Fixes #268

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 22:25:06 +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 7ea8698db9 Fix unbound CoroutineScope in NotificationReplyReceiver (#265) (#331)
This commit addresses issue #265 where the NotificationReplyReceiver
used an unbound CoroutineScope when handling asynchronous reply saves
to the Room database. If the DAO operation hangs, the PendingResult.finish()
method is never called, which could lead to an Application Not Responding (ANR)
error.

Changes made:
- Wrapped the Room upsert operation inside a `withTimeout(5000L)` block
  within the IO coroutine in `NotificationReplyReceiver.kt`.
- Ensures that if the operation exceeds 5 seconds, a TimeoutCancellationException
  is thrown and caught, allowing `pendingResult.finish()` to execute in the
  `finally` block.
- Added a unit test in `NotificationReplyReceiverTest.kt` to verify that
  if the mock DAO hangs indefinitely, the PendingResult is still correctly finished
  after the timeout.

Impact:
- Prevents potential ANRs caused by stuck local DB transactions during inline
  notification replies.
2026-06-23 11:56:10 +03:00
M57andM57 c44f4c3068 test(#291): add coverage for 3 critical untested components (#300)
* test(#291): add coverage for 3 critical untested components

- NavigationController dedup guard (line 45): 14 pure unit tests for
  navigateTo, resetTo, goBack, primary-screen clearing, and null guards.
- NotificationReplyReceiver: 11 MockK-based tests covering the full
  WS+Room+notification flow: valid reply sends WS message, persists to
  Room with correct entity shape, shows replied confirmation notification,
  finishes PendingResult, and gracefully handles null/blank inputs and
  Room failures.
- ChatNotificationService isAppInForeground flag: 5 tests verifying
  companion object toggle, volatile modifier (thread safety), default
  false state, and idempotent calls.
- Fix: add @Volatile to isAppInForeground (was a plain var read from
  background coroutine and written from UI thread)

Closes #291

* fix(#291): fix MockK type errors in test compilation

* fix(#291): fix test assertions and goAsync mock for CI

* fix(#291): wrap final BroadcastReceiver.goAsync() with open Kotlin wrapper

* chore: trigger CI on latest fix commit

* fix(#291): make NotificationReplyReceiver open so tests can subclass it

* fix(#291): extract buildReplyNotification() to avoid 'not mocked' framework error

NotificationCompat.Builder.build() internally calls android.app.Notification.Builder
methods (e.g. setWhen) that throw RuntimeException with 'Method setWhen in
android.app.Notification.Builder not mocked' in unit tests — the standard Android
unit test environment does not provide real framework implementations.

Extracting notification creation into an open method (same pattern as goAsyncCompat())
lets tests override it to return a mock Notification, avoiding the framework call
entirely.

* style: fix ktlint blank-line-before-declaration in test

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-22 06:49:19 +03:00
M57 bfef3613fa fix(#240): parse session_id from params root (#242)
* fix(#240): parse session_id from params root

Allows correct session mapping when opening app via notification.

* fix(#240): set MainActivity launchMode to singleTop

Prevents activity destruction and WebSocket reconnection when tapping notifications.

* fix(#240): save inline notification replies to Room database

* fix(#240): refresh current session when app comes to foreground

* fix(#240): use uppercase role string to match Mapper mapping expectations

* style: format NotificationReplyReceiver.kt to satisfy ktlint
2026-06-21 19:04:54 +03:00
Hy4ri ce5428f66b feat(#116): add direct reply action to background notifications 2026-06-19 23:44:40 +03:00