Commit Graph
114 Commits
Author SHA1 Message Date
M57 d16a2ea35c feat(chat): use backend usage deltas for assistant stats 2026-09-16 01:34:05 +03:00
Alfredo Ortegón Sepúlveda aed8ba4a6d feat(sessions): show project and last activity on history cards (#1133) 2026-09-16 00:14:09 +03:00
M57 528ec4f28f feat(#1121): add multiplexed gateway awareness (#1127) 2026-09-15 15:56:32 +03:00
M57 e4be8b146e fix(#1119): handle gateway server requests (#1125) 2026-09-15 13:09:11 +03:00
M57 fb0f93c000 fix(#1118): use supported replay params (#1123) 2026-09-15 11:16:20 +03:00
M57 da03500b30 feat(kanban): add KanbanSyncCoordinator and harden WebSocket event decoding 2026-09-14 22:51:20 +03:00
M57 c57e056d71 feat(kanban): enforce board scoping and local selection in KanbanViewModel 2026-09-14 22:23:01 +03:00
M57 a486191d9d feat(kanban): add typed models and KanbanApiService for desktop parity 2026-09-14 22:12:30 +03:00
M57 0a2c782139 fix(chat): keep context meter aligned after model swaps (#1103) 2026-09-13 19:39:33 +03:00
M57 7e8af8db01 feat(sessions): show live status for active sessions in History (#1101) (#1104) 2026-09-13 14:45:33 +03:00
M57 52e0104cd2 feat(connectors): surface active session integrations (#1091) (#1100) 2026-09-13 13:41:13 +03:00
M57 29bc367aec feat(subagents): live active subagents roster and transcript tail (#1089) (#1098) 2026-09-13 11:06:34 +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
M57 065c7fd75f feat(chat): display token usage and tps in message footers (#1094) 2026-09-12 16:04:13 +03:00
M57 5dc3896237 feat(sessions): branch tree flattening with group recency and collapsible pinned section (#1087) 2026-09-12 01:41:24 +03:00
M57 1cdc0dbfc0 fix(ws): gateway liveness, replay correctness, and streaming flush (#1085) 2026-09-12 00:19:13 +03:00
AlexDev_ acc9c7ce9a feat(sessions): separate conversation and automation history 2026-09-05 19:55:57 +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
M57 6d2362ba8a feat(chat): support multi-select, batch multi-question clarify prompts, and expiry (fixes #1045) (#1055) 2026-09-03 21:38:57 +03:00
M57 fa17c95296 fix(chat): support batch clarify.request payload and route question_id on respond (fixes #1045) (#1046) 2026-09-03 12:50:13 +03:00
M57 d8385a2b02 feat(ws): adopt WS ping method for heartbeat and latency tracking (#1036) 2026-09-01 19:44:39 +03:00
M57andM57 21db9bfdab fix(memory): scope memory endpoints to active profile and auto-reload on profile switch (#1035)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-09-01 19:20:18 +03:00
M57andM57 908255f5ad feat: support session-level hidden flag in SessionListResponse and SessionsScreen (#1033)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-09-01 16:56:28 +03:00
M57andM57 ceb4813218 feat(chat): support hierarchical nested subtasks in Todos via parent property (#1018) (#1028)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-31 22:09:15 +03:00
M57andM57 3fd9ac767f feat(ws): adopt event replay via session.events.since on reconnect (#1016) (#1027)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-31 19:32:17 +03:00
M57andM57 cfe3e736fd chore: upgrade ktlint to 1.8.0 and format codebase (#1024)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-31 12:27:40 +03:00
M57andM57 5bc6e5f855 feat(settings): in-app chat font size setting (#1004) (#1005)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-29 11:36:56 +03:00
M57andM57 f04d878358 feat(bots): add multi-agent group chat collaboration room (#998)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-28 17:50:37 +03:00
M57andM57 4d9dfb99db feat(db): migrate to androidx.room3 3.0.2 with SQLCipherDriver (#994)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-27 13:47:32 +03:00
AlexDev_ 0ae5ed4db2 feat(chat): stabilize multi-image attachment picker (#986) 2026-08-27 10:50:55 +03:00
M57 c028077647 feat(models): support bot metadata, canonical sessions, and group sync in Profile models (#973) 2026-08-27 01:48:43 +03:00
M57andM57 1f1ebf3597 feat(update): redesign in-app updater with smart cooldown, release notes, and in-place dialog (#971)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-26 07:42:41 +03:00
M57andM57 9217f9f8b6 fix(sessions): tolerate integer and string boolean formats for pinned field (#966) (#967)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-25 17:05:14 +03:00
M57andM57 764cb3455f fix(auth): await cookie scope hydration on cold start (#961) (#962)
* fix(auth): await cookie scope hydration before ticket mint and request loading (#961)

* refactor(auth): scope latch activation to active store hydration

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-23 23:57:37 +03:00
M57andhy4ri bb400881e6 ws: harden HermesWsClient (stale-socket watchdog, JSON ticket parse, dead-branch removal) (#954)
* ws: watchdog cancels stale sockets to trigger reconnect

* ws: parse ws-ticket response with kotlinx.serialization

* ws: remove unreachable buffer-overflow warning

* style: ktlint format ws client tests

---------

Co-authored-by: hy4ri <hy4ri@users.noreply.github.com>
2026-08-22 18:49:50 +03:00
M57andHy4ri 59bcbdfe3c build: Android 17 (API 37) + okhttp 5.5.0 + LAN local-network permission gate (#941)
* build: compileSdk 36 -> 37 + okhttp 5.4.0 -> 5.5.0

Take okhttp 5.5.0 (folds in dependabot PR #940) which hard-requires
compileSdk 37 via AAR metadata. targetSdk stays 36 for now.

* build: targetSdk 36 -> 37 (Android 17) + ACCESS_LOCAL_NETWORK gating

Opt into Android 17 runtime behavior. The app connects to a gateway
(default 127.0.0.1:9119, plus LAN + remote), so targeting 37 forces the
new ACCESS_LOCAL_NETWORK permission for LAN hosts.

- AndroidManifest: declare ACCESS_LOCAL_NETWORK (harmless below API 37)
- LocalNetwork.kt: pure detector + needsLocalNetworkPermission(baseUrl)
  (loopback + public remote exempt; RFC1918/link-local/CGNAT gated)
- ConnectViewModel: requestConnect() gates on the permission; screen
  launches the prompt; onLanPermissionResult proceeds or shows a clear error
- flake.nix: android platform 36 -> 37
- Tests: LocalNetworkTest + ConnectPermissionGateTest (SDK_INT / permission
  branching mocked, no device needed)
- Task 8 finding: assistant services (VoiceInteraction*/Recognition) and the
  remoteMessaging ChatNotificationService do NOT hit the Android 17 mic/
  while-in-use foreground hardening, so no change required.

* fix: ktlint + @Composable LocalContext hoist (was blocking CI)

- ConnectScreen: hoist LocalContext.current out of onClick lambda
  (calling a @Composable inside a non-composable lambda broke compileDebugKotlin)
- ktlint --format: multiline-expression-wrapping, function-signature,
  no-empty-first-line-in-class-body, no-unused-imports
- LocalNetwork.kt: merge consecutive KDocs (no-consecutive-comments)

flake.nix buildToolsVersion/platformToolsVersion intentionally stay 36.0.0
— they are decoupled from compileSdk; only platformVersions needs 37.

* fix: suspend mock (coEvery) + flake toolchain pinned to 37.0.0

- ConnectPermissionGateTest: getStatus() is suspend → use coEvery (was
  breaking compileDebugUnitTestKotlin)
- flake.nix: buildToolsVersion/platformToolsVersion/cmdLineToolsVersion
  pinned to 37.0.0 (verified against pinned nixpkgs 241313f — resolves to
  android-sdk-*-37.0.0 derivations; platformVersions already 37)

* fix(flake): revert cmdLineToolsVersion to 11.0 (not API-aligned)

cmdline-tools uses its own versioning (latest = 21.0, never 37.0.0) —
nixpkgs rejects 37.0.0 with 'version missing'. build-tools + platform-tools
stay 37.0.0 (verified resolve to android-sdk-*-37.0.0). flake now evals
clean against pinned nixpkgs 241313f.

* flake: track nixos-unstable + pin toolchain to latest (cmdline 22.0, platform-tools 37.0.1, build-tools 37.0.0, platform 37.1)

- inputs.nixpkgs -> nixos-unstable, flake.lock bumped to rev ffb3c9b7 (2026-08-19)
- cmdLineToolsVersion 11.0 -> 22.0 (own versioning, not API-aligned; v22_0 confirms)
- platformToolsVersion 37.0.0 -> 37.0.1
- buildToolsVersion stays 37.0.0
- platformVersions 37 -> 37.1
- verified: nix flake check --no-build -> all checks passed on hyari

* fix: unit tests — Uri stub + Build.SDK_INT final-field mock

- LocalNetwork.kt: needsLocalNetworkPermission uses java.net.URI instead of
  android.net.Uri (latter is a non-functional stub in JVM unit tests, its host
  returns null -> gate always false). Added pure requiresLocalNetworkPermission(sdkInt, baseUrl).
- ConnectViewModel: inject sdkVersion (package-private) instead of reading the
  final static Build.VERSION.SDK_INT directly; gate via requiresLocalNetworkPermission.
- ConnectPermissionGateTest: drop mockkStatic(Build.VERSION) (MockKException on
  final static field, and against repo convention); inject sdkVersion; add pure
  gate-decision matrix. Fixes 7 red tests.

* fix(test): remove illegal ':' from test function names (compile error)

* fix(test): order getString mocks so specific resources win over wildcard

* fix(flake): include android-37.0 system image so the emulator can boot

Google only publishes the android-37.0 system image (no 37.1 image exists
yet). With platformVersions=["37.1"] composeAndroidPackages found no matching
system image, leaving system-images/ empty and every AVD FATALing with
'Broken AVD system path'. Adding 37.0 pulls the published image so an AVD boots
(verified: hermes37 AVD boots Android 17 / API 37 on hyari). compileSdk/targetSdk
stay 37.

Also bump AGENTS.md SDK note 36 -> 37 (was stale).

* fix(flake): platformVersions 37.1 -> 37.0

Drop 37.1; 37.0 provides both the SDK platform for compileSdk 37 and the
published android-37.0 system image the emulator needs.

* fix(flake): remove stray tool-scratch line leaked into flake.nix (line 116)

A terminal tool's /tmp scratch rename line got appended to flake.nix, making
it invalid Nix. Removed; flake now parses + 'nix flake check' passes on hyari.

* fix(build): pin buildToolsVersion 37.0.0 to match nix SDK

AGP defaulted build-tools to 36.0.0, which isn't in the nix-managed SDK
(only 37.0.0 is provisioned). Local ./gradlew builds then tried to *install*
36.0.0 into the immutable /nix/store and failed. Pin 37.0.0 so the local
nix devShell builds. CI uses its own SDK so unaffected.

---------

Co-authored-by: Hy4ri <Hy4ri@users.noreply.github.com>
2026-08-20 14:35:42 +03:00
M57andM57 82e2d787af fix(sessions): show fork icon + order number on nested forked sessions (#934)
* fix(sessions): fork icon + depth number, no indentation

Multiply-nested forked sessions rendered with deep indentation that crushed
the card off-screen and made titles unreadable, and the per-sibling order
number was uninformative (mostly "1").

- SessionTreeItem: replace forkIndex (sibling order) with forkDepth (nesting
  level: 1 = child of a root, 2 = fork of a fork, ...); flattenSessionTree
  populates it.
- SessionCard: render CallSplit fork icon + the depth number (primary tint).
  Removed the depth-based start padding so names stay full-width readable —
  the icon+number now carries the structure instead of indentation.
- strings: sessions_fork_indicator (en/zh/ko).
- SessionTreeTest: assert isFork/forkDepth across nested fixtures.

No backend change — backend already returns parent_session_id per SessionInfo.

* test(ws): make testAutoReconnect tolerate transient RECONNECTING state

The test gated on connectionStatus.first { it == RECONNECTING } with a
15s timeout. With the test backoff forced to 0ms the RECONNECTING state is
emitted + consumed before the collector attaches, so the wait missed it and
timed out (kotlinx.coroutines.TimeoutCancellationException) — the flake CI
saw under parallel load.

- Replace the brittle RECONNECTING .first{} wait with a tolerant check that
  accepts RECONNECTING-or-later (the reconnect clearly fired).
- Keep connect2Latch (second socket opening) as the definitive proof + add a
  final CONNECTED assertion.
- Reset the test backoff in a finally so later tests never inherit 0ms.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-18 17:06:51 +03:00
M57andM57 50a0f30d8f feat(#920): adopt GET /api/files/stream for seekable audio and video playback (#930)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-18 12:02:36 +03:00
M57andM57 acd82c0085 feat(#919): adopt session.usage WS push event for real-time context & compression updates (#927)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-17 23:31:07 +03:00
M57andM57 d8be45b8fc fix: reuse cached gateway downloads within a 10-minute TTL (#915)
Every tap on a gateway attachment re-downloaded the whole file: the
cache write used a random UUID prefix, so no two fetches ever produced
the same filename and old copies were never looked up.

Cache files are now named deterministically from the gateway path
(UUID3 hash + filename), so repeat opens within the TTL (10 min) reuse
the existing download and skip the network entirely. Stale entries fall
through to a fresh fetch that overwrites the cache via temp-file +
rename, so a failed re-download never destroys a previously-good copy.
Content types are remembered in-memory per path so cache hits still
hand the viewer the right MIME.

Also adds isFreshCacheFile()/cacheFileNameFor() unit tests.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-15 23:05:10 +03:00
M57andM57 b653903fa8 fix: stream gateway downloads to disk instead of slurping into RAM (#914)
* fix: stream gateway downloads to disk instead of slurping into RAM

Opening or saving a ~100MB MEDIA file OOMs the app: fetch() read the
whole response body into a single ByteArray (peak heap ~2x file size)
before writing it to cache. A 102MB docx (~97MiB) passed the server's
100MiB cap but blew the phone heap (192MiB growth limit) on both open
and save.

fetch() now streams the body to a cache file in 64KiB chunks and
GatewayFile carries the written file instead of bytes; save copies the
cache file to the destination chunked. Cancellation-safe (partial file
deleted, CancellationException never swallowed), UUID-named cache
files, 24h sweep.

Closes #913

* fix: stub app.cacheDir/applicationContext in attachment tests

The streaming fetch builds File(cacheDir, ...) from the application
context's cacheDir; the affected tests never stubbed it, so the mock
returned a bogus File (null path) and java.io.File's constructor NPE'd
before fetch was even called. Stub cacheDir + applicationContext
(returning app itself, matching the existing success-path test) in the
five open/save tests. copyChunked is also stubbed to copy for real in
the save-writes test since mockkObject mocks the whole object.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-15 23:05:10 +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 85087c70c7 feat: surface /api/status memory+disk pressure on connect/status screens (#903) (#908)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-15 13:00:08 +03:00
gslzwhlandhermes-i18n fdbd98af3e Add Simplified Chinese (zh) localization (#901)
* feat: add Simplified Chinese (zh) localization

- Add complete values-zh/strings.xml (921 strings, all keys covered)
- Translate 107 hardcoded UI strings across 34 Kotlin files
- Add 简体中文 option to in-app language picker
- Terminology aligned with official Hermes dashboard zh translations

* fix: resource-ize hardcoded Chinese strings per review

Address Hy4ri's review on PR #901:
- Move 104 hardcoded Chinese UI strings to string resources
  (English in values/strings.xml, Chinese in values-zh/strings.xml)
  referenced via stringResource()
- SystemViewModel labels -> @StringRes + AndroidViewModel with
  resource-based toast templates
- Make TodoTaskCardTest assertion locale-agnostic via getString()
- Update cookie tests to assert new host-agnostic session cookie
  behavior (WiFi/Tailscale switch-safe)
- Session cookie mirroring to WILDCARD bucket survives host switches

* style: apply ktlintFormat per review

---------

Co-authored-by: hermes-i18n <hermes-i18n@localhost>
2026-08-15 11:33:43 +03:00
M57andM57 90c1e8dc5f fix: tab reloads actually reload — kanban switch spam, plugins/skills refresh, ws mint (#897)
- ws: ticket-mint response body was read on the calling (main) thread —
  the socket read threw NetworkOnMainThreadException, the mint failed,
  and the kanban events stream never connected (board stuck Offline).
  execute() + body read now run entirely on Dispatchers.IO; same fix in
  DashboardSessionTokenRefresher.fetch (loopback mint path).
- kanban: re-selecting the already-active board reloads data and
  reconnects events WITHOUT the redundant POST /boards/{id}/switch that
  refresh fired on every tap.
- plugins: top-bar Refresh + pull-to-refresh wired through HermesScaffold
  (previously no reload control at all).
- skills: Refresh icon now does a plain GET reload; the heavy hub-update
  POST moved to the cloud icon so refresh is no longer disguised as update.
- test: mock the 3-arg Log.w overload (catch now logs the full stack).

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-13 14:27:36 +03:00
M57andM57 daf33dcb4c feat(update): notify about app updates on launch (chat banner) (#891)
Fires one silent update check per installed version at app start
(UpdateNoticeManager, release builds only) and shows a non-blocking
banner on the chat screen when a newer release exists. The About tab
adopts the launch result instead of re-pinging GitHub; dismissals
return on the next launch via the persisted latest tag.

Closes #890

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-13 01:08:42 +03:00
M57andM57 683a24becf fix(profiles): connection-profile switch re-dials the WebSocket (#888)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-12 23:27:08 +03:00
M57andM57 f931565c0d feat(settings): in-app update from About tab (issue #867) (#875)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-08-10 20:02:42 +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