Commit Graph
31 Commits
Author SHA1 Message Date
AlexDev_ 0d4c0b40ad feat(chat): show session-scoped reply failure details (#1255) 2026-09-24 21:37:04 +03:00
M57 dc77b306b6 feat: auto-dismiss reply notifications when read in chat (#1195) 2026-09-19 22:58:43 +03:00
M57 759d59b9f9 feat: add setting to keep connection alive in background (#1196) 2026-09-19 18:28:48 +03:00
M57andhy4ri 220779bf19 perf(#1171): async AuthManager startup with readiness gate and lazy database (#1182)
Co-authored-by: hy4ri <hy4ri@users.noreply.github.com>
2026-09-17 20:54:03 +03:00
M57 69cd34d289 feat(vault): credential vault status and mid-turn auth prompt handling (#1090) (#1097) 2026-09-13 00:20:07 +03:00
AlexDev_ 7223c60a97 fix(notification): defer shutdown until foreground promotion 2026-09-06 14:24:41 +02:00
M57 bd0b4c2d55 feat(chat): full approval.request support (#1057) 2026-09-03 22:33:15 +03:00
M57 077ad1fa81 feat(chat): full sudo.request support with expire, cancel-unblock, secret fields (#1056) 2026-09-03 22:08:07 +03:00
AlexDev_ 3b033a025b fix(notification): disconnect idle background socket (#858)
* fix(notification): disconnect idle background socket

* fix(notification): clear rejected prompt state
2026-08-09 12:06:59 +03:00
M57andM57 3e9672f0ad fix(security): validate intent action before consuming notification session id (issue #832) (#845)
MainActivity is exported (launcher requirement) and consumed the
session-id extra from any incoming intent without checking the action —
a foreign app could launch it explicitly and force-navigate to a chat
screen. Guard consumeNotificationIntent() on the app's own notification
action and hoist the action string into a shared const so
ChatNotificationService and MainActivity can never drift.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-07 22:42:39 +03:00
AlexDev_ 356ccc98b5 fix(notification): resume stored session before reply (#805) 2026-08-04 19:54:49 +03:00
M57 af6fd9b346 🔒 Secure RemoteInput PendingIntent boundaries (#800)
Added `setPackage(packageName)` to the explicitly defined reply intent
before wrapping it in the mutable PendingIntent for remote input.

RemoteInput intents strictly require `FLAG_MUTABLE` so the Android OS
can append the user's text reply. To use `FLAG_MUTABLE` securely
and prevent unintentional component delivery, explicit component
boundaries (like `setPackage`) must be defined.
2026-08-04 18:17:25 +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 dc74eb4bc5 notification: harden reply + content PendingIntents (explicit action, FLAG_ONE_SHOT) (#753) 2026-07-31 23:38:37 +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 ecbea6d2b9 fix(#267): prevent ghost notifications on app foreground transition (#328)
- Replace volatile boolean with AtomicBoolean for thread-safe state access
- Add a 500ms debounce to notification logic to account for delayed foreground transitions
- Update ChatNotificationService unit tests to reflect the new AtomicBoolean state type

This fixes a race condition where a message arriving just as the user opens the app would incorrectly trigger a foreground notification.
2026-06-23 12:08:03 +03:00
M57 a27512cee4 fix: hide chat notification content on lock screen (#333)
Set `NotificationCompat.VISIBILITY_PRIVATE` on the chat reply
notification builder. This prevents the first 100 characters of a
message from leaking to the lock screen, addressing security concern
SEC-09.

Resolves #262
2026-06-23 11:42:15 +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 21744fd951 fix: replace .apply block with direct property/extra calls for ktlint 2026-06-21 17:22:35 +03:00
M57 fea7820740 fix: extract long toast string to val, fix indentation 2026-06-21 17:18:59 +03:00
M57 ab92b31d2e fix: ktlint wrapping violations on toast message and intent builder 2026-06-21 17:16:05 +03:00
M57 d2e59b2741 fix(#214): notification tap now navigates to correct session
Root cause: MainActivity had no onNewIntent override. When the app was
backgrounded and a notification was tapped, Android brought the existing
task to the foreground and delivered the intent via onNewIntent — but
since it wasn't overridden, the session ID from the notification was
silently dropped and the user saw the last-active session.

Fix:
1. Changed notificationSessionId from a local val to a Compose
   mutableStateOf so it's observable across recompositions.
2. Added onNewIntent override that reads the session ID from the
   incoming intent and updates the observable state.
3. Added FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP to the
   notification PendingIntent for reliable task management.
2026-06-21 16:40:30 +03:00
Hy4ri acd6cbf751 refactor(#78): extract notification strings to resources 2026-06-21 11:31:09 +03:00
M57andM57 ca21e22147 ci(#71): change FGS type from dataSync to remoteMessaging for indefinite WebSocket listener (#162)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 14:07:46 +03:00
M57andM57 f5edf1569c fix(#141): notification tap opens original session instead of new session (#147)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-20 10:10:10 +03:00
Hy4ri ce5428f66b feat(#116): add direct reply action to background notifications 2026-06-19 23:44:40 +03:00
Hy4ri a7d730cae5 fix(#114): reduce intrusion of background service notification 2026-06-19 23:36:31 +03:00
M57andM57 451412851f fix(#69): replace mipmap launcher icon with proper notification vector drawable (#95)
Notification smallIcon must use a simple mono vector drawable, not a
launcher mipmap. Launcher icons render broken in the status bar on
many devices.

- Added app/src/main/res/drawable/ic_notification.xml (simple info icon)
- Updated both showReplyNotification and buildForegroundNotification to
  reference R.drawable.ic_notification instead of R.mipmap.ic_launcher

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-19 22:15:36 +03:00
Hy4ri 9e3a7ab339 fix: always show bottom bar, resolve chat input double padding, and format codebase 2026-06-19 18:14:41 +03:00
M57andM57 5130c2e308 feat(chat): message persistence + background notifications (#2)
* feat(chat): message persistence + background notifications

Two critical gaps for daily-driver use:

1. Message persistence (Room database):
   - New ChatMessageEntity / ChatMessageDao / HermesDatabase layer
   - Every user message, streaming token, tool result, and assistant
     reply is written to a local Room DB as it arrives
   - switchSession loads cached messages instantly from Room before
     the REST call returns — no more blank screen while loading
   - Session resume also loads from cache first
   - Messages survive app kills, process death, and configuration
     changes
   - ChatViewModel changed from ViewModel() to AndroidViewModel(app)
     to access the database context

2. Background notifications (foreground service):
   - ChatNotificationService keeps the WebSocket alive while the app
     is backgrounded and posts a high-priority notification when a
     MessageComplete or ClarifyRequest event arrives while the app
     is not in the foreground
   - Lifecycle wired via LifecycleEventObserver in ChatScreen:
     ON_STOP starts the service, ON_START stops it
   - Tapping the notification returns to the chat
   - New permissions: POST_NOTIFICATIONS, FOREGROUND_SERVICE,
     FOREGROUND_SERVICE_DATA_SYNC

Build changes:
   - Added Room 2.7.1 + KSP to version catalog
   - Added WorkManager 2.10.0 (reserved for future polling fallback)
   - Updated ChatViewModelTest to mock HermesDatabase + use
     AndroidViewModel constructor

* fix(build): use KSP 2.3.9 (new versioning scheme for Kotlin 2.3.x)

* fix(build): add Room schemaDirectory (required by Room Gradle plugin)

* fix(compile): add else branch to WsEvent when expression (exhaustiveness)

* ci(release): add release workflow + R8 minification + proguard rules

New .github/workflows/release.yml:
- Triggers on tag pushes (v*) and manual workflow_dispatch
- Runs full CI (ktlint + lint + test) before building
- Builds assembleRelease with version injection from tag
- Creates a GitHub Release with auto-generated changelog
- Attaches the release APK to the release

Build changes:
- Release builds now use R8 minification (isMinifyEnabled=true)
  and resource shrinking (isShrinkResources=true) for smaller APKs
- versionCode/versionName injectable via -PversionCode / -PversionName
  Gradle properties (passed by CI from the git tag)
- Local dev builds default to versionCode=1, versionName=1.0-dev

ProGuard rules:
- Keep all data model classes (Gson reflection)
- Keep Room entities and RoomDatabase subclasses
- Keep OkHttp/Retrofit interface methods
- Suppress warnings for Compose, Security Crypto, Coroutines

* feat(ci): release workflow + proguard rules + test fix

Release workflow (.github/workflows/release.yml):
  - Triggers on tag push (v*) or manual workflow_dispatch
  - Two jobs: ci (ktlint+lint+test) → build-release
  - Builds a minified, shrunk release APK with version from tag
  - Creates a GitHub Release with auto-generated release notes
  - Attaches the APK as a release asset

Build config:
  - versionCode/versionName now injected from CI via -P flags
  - Release build enables isMinifyEnabled + isShrinkResources
  - proguard-rules.pro: keep rules for Gson models, Room entities,
    OkHttp/Retrofit interfaces, Coroutines, Compose, BuildConfig

Test fix:
  - ChatViewModelTest: use coEvery for suspend DAO methods instead
    of every (compile error — suspend functions need coEvery)
  - Added stubs for all suspend DAO methods used by the ViewModel

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-18 03:23:38 +03:00