Commit graph

1246 commits

Author SHA1 Message Date
bergeouss
21ba37c486 fix: session list race condition (#1430) + read-only fs guard (#1470)
#1430 — renderSessionList() had no staleness guard. Multiple concurrent
callers (message send, rename, session switch) could race, allowing a
slower older API response to overwrite _allSessions with stale data.
Added a generation counter that increments on each call and discards
responses from superseded generations.

#1470 — docker_init.bash unconditionally called groupmod/usermod even
on read-only root filesystems (podman with read_only=true). Added a
writability check for /etc/group and /etc/passwd. If read-only and
UID/GID already match, the mod is skipped gracefully. If they don't
match, a clear error message suggests setting matching IDs or disabling
read_only mode.
2026-05-04 16:51:53 +00:00
nesquena-hermes
25cb35ee1a
Merge pull request #1632 from nesquena/stage-293
Release v0.50.293 — 3-PR batch (profile isolation trio + agent version + #1597 follow-up)
2026-05-04 09:36:37 -07:00
Hermes Agent
f3e066b53c chore(release): stamp v0.50.293 — 3-PR batch + 2 Opus follow-ups absorbed
Constituent PRs:
  #1627 by @franksong2702 — show Hermes Agent version (closes #1606)
  #1629 by @nesquena-hermes — profile isolation trio (closes #1611, #1612, #1614)
  #1630 by @Michaelyklam — provider config cleanup regression test (#1597 follow-up)

Opus advisor SHIP verdict + 2 SHOULD-FIX absorbed in-release:
- load_projects() re-reads from disk inside lock to close migration startup race
- _detect_agent_version() uses --dirty for symmetry with _detect_webui_version()

4142 → 4180 tests passing.
2026-05-04 16:33:57 +00:00
test
838645fd50 Stage 293: PR #1629 — profile isolation trio (closes #1611, #1612, #1614) by @nesquena-hermes — APPROVED 2026-05-04 16:21:29 +00:00
test
341b4c7abd Stage 293: PR #1627 — show Hermes Agent version in Settings (closes #1606) by @franksong2702 2026-05-04 16:20:39 +00:00
test
7680b1de45 Stage 293: PR #1630 — provider config cleanup regression test (#1597 follow-up) by @Michaelyklam 2026-05-04 16:20:39 +00:00
nesquena-hermes
6bc0f9c4d5 Apply Opus pre-release SHOULD-FIX + NITs (in-PR per release policy)
SHOULD-FIX #1 (renamed-root client cross-alias): drop strict-equality client
filter at static/sessions.js:1853. Server-side _profiles_match cross-aliases
'default'-tagged rows to a renamed root 'kinni'; the strict-equality client
would reject them, dropping every legacy session for renamed-root users. The
server is now solely authoritative for profile scoping.

SHOULD-FIX #2 (messaging-source dedupe ordering): _keep_latest_messaging_session_per_source
now runs AFTER the profile filter at api/routes.py:2078. Before, it ran on
the merged-cross-profile list with profile-blind keys, discarding the older
profile's row across profiles before the scope filter — leaving zero rows for
any messaging identity the active profile shared with another profile.

NIT #3: _projects_migrated flag now set only AFTER successful save_projects.
NIT #4: cleaned dead test code in test_is_root_profile_invalidation_drops_stale.
NIT #5: _create_profile_fallback's clone_from=='default' literal now routes
through _is_root_profile() for parity with the 5 other callsites.

+2 regression tests pin the SHOULD-FIX shapes:
- test_keep_latest_messaging_runs_after_profile_filter (source-string ordering)
- test_static_sessions_js_trusts_server_profile_scoping (no client re-filter)

4173 -> 4175 tests pass. 0 regressions.
2026-05-04 16:17:26 +00:00
Michael Lam
b6c695e1ab test: cover provider config cleanup path 2026-05-04 09:04:07 -07:00
nesquena-hermes
e8862632ed fix(profiles): scope sessions, projects, and root-profile resolution to active profile (#1611, #1612, #1614)
Closes #1611 — /api/sessions filters by active profile by default; ?all_profiles=1
opt-in for aggregate views; new _profiles_match() helper honours renamed-root
cross-aliasing; static/sessions.js drops the s.is_cli_session bypass; toggle-on
re-fetches with all_profiles=1 instead of slicing client-cached rows.

Closes #1612 — new _is_root_profile() central helper consults list_profiles_api()
for is_default=True matches alongside the legacy 'default' alias. Replaces five
literal-default callsites in api/profiles.py. Memoized with explicit invalidation
hooks at create + delete. Sticky active_profile file write now stores '' for
renamed root, consistent with the legacy empty==root contract.

Closes #1614 — projects carry a profile field stamped at create-time;
/api/projects filters by active profile; /api/projects/{create,rename,delete}
and /api/session/move reject ops on cross-profile projects with 404; new
_PROJECTS_MIGRATION migration in load_projects() back-tags untagged projects
from any session that uses them, fall back to 'default'; ensure_cron_project
keys lookup by (name, profile) so each profile gets its own Cron Jobs project.

31 regression tests (9+11+11) pin the renamed-root resolution, server-side
profile scoping shape, helper invariants, cross-alias matching, migration
behavior, and active-profile guards on every project mutation endpoint.
4148 tests pass.

Reporter: @stefanpieter

Co-authored-by: stefanpieter <noreply@github.com>
2026-05-04 16:03:05 +00:00
Frank Song
59efb42dcd Show Hermes Agent version in settings 2026-05-04 23:57:56 +08:00
nesquena-hermes
95200419ee
Merge pull request #1626 from nesquena/stage-292
Release v0.50.292 — 12-PR batch (multi-tab SSE + subpath routes + 3 follow-ups + UX polish)
2026-05-04 08:50:46 -07:00
Hermes Agent
1549a10510 chore(release): stamp v0.50.292 — 12-PR batch + Opus follow-ups absorbed
Constituent PRs:
  #1597 by @Michaelyklam — pytest config-path isolation
  #1598 by @Michaelyklam — multi-tab SSE broadcast (closes #1584)
  #1599 by @Sanjays2402 — _pending_started_at truthy-check (closes #1595)
  #1600 by @Michaelyklam — streaming markdown subpath/fallback
  #1601 by @Michaelyklam — subpath frontend routes
  #1602 by @ai-ag2026 — cross-source continuation
  #1603 by @ai-ag2026 — git remote name preservation
  #1605 by @ai-ag2026 — update banner branch labels
  #1608 by @franksong2702 — cron broad-except removal (closes #1578)
  #1609 by @franksong2702 — server.py socket cleanup (closes #1583)
  #1621 by @franksong2702 — fork indicator polish (fixes #1613)
  #1622 by @s905060 — paste text-with-image (closes #1620)

Opus advisor SHIP verdict + 2 SHOULD-FIX absorbed in-release:
  • #1598 ordering race fixed (offline-buffer replay moved inside lock)
  • #1601 sessions.js:1440 gateway SSE probe baseURI parity fix

4117 → 4142 tests passing.
2026-05-04 15:45:41 +00:00
test
06a71563de Stage 292: PR #1621 — polish forked session indicator by @franksong2702 2026-05-04 15:34:21 +00:00
test
21eb8a89bf Stage 292: PR #1598 — broadcast SSE stream events to multiple tabs (closes #1584) by @Michaelyklam 2026-05-04 15:34:17 +00:00
test
8a10532d29 Stage 292: PR #1601 — keep frontend routes under subpath mounts by @Michaelyklam 2026-05-04 15:34:08 +00:00
test
6f8424e5b7 Stage 292: PR #1622 — don't attach image on paste when clipboard has text (closes #1620) by @s905060 2026-05-04 15:33:32 +00:00
test
b6702fbeae Stage 292: PR #1602 — keep cross-source continuations separate in sidebar by @ai-ag2026 2026-05-04 15:33:32 +00:00
test
51848fb67d Stage 292: PR #1603 — preserve git remote names in update links by @ai-ag2026 2026-05-04 15:33:32 +00:00
test
165356e744 Stage 292: PR #1608 — tighten worker-side broad-except in _run_cron_tracked (closes #1578) by @franksong2702 2026-05-04 15:33:32 +00:00
test
3985dadda6 Stage 292: PR #1609 — clean up dead socket code and fix macOS keepalive (closes #1583) by @franksong2702 2026-05-04 15:33:32 +00:00
test
ead91878ef Stage 292: PR #1605 — show update branches in banner labels by @ai-ag2026 2026-05-04 15:33:32 +00:00
test
e5a5720e00 Stage 292: PR #1600 — render streaming markdown on subpath mounts by @Michaelyklam 2026-05-04 15:33:32 +00:00
test
5b4ab72452 Stage 292: PR #1597 — isolate pytest Hermes config path by @Michaelyklam 2026-05-04 15:33:32 +00:00
test
38f9ece4f2 Stage 292: PR #1599 — streaming truthy-check for _pending_started_at fallback (closes #1595) by @Sanjays2402 2026-05-04 15:33:32 +00:00
Jash Lee
1ad0ab42e5 Fix #1620: don't attach image on paste when clipboard also has text
When the clipboard carries both text and an image (rich-text sources like
Notes, Word, Slack, browser selection attach a rendered preview alongside
the plain text), the paste handler in static/boot.js unconditionally
called e.preventDefault() and routed the image into addFiles(), silently
discarding the text payload.

Fix:
  - Detect text in the clipboard via items[].kind === 'string' &&
    (type === 'text/plain' || type === 'text/html'). When present, return
    early so the browser's default text-paste runs.
  - Tighten the image filter to kind === 'file' && type.startsWith('image/')
    so string items advertising an image MIME (e.g. text/html with an
    embedded data URI) are not misclassified as a true screenshot paste.

Pure-screenshot paste (image-only clipboard, e.g. Cmd+Shift+Ctrl+4 on macOS)
is unchanged.

Adds tests/test_1620_paste_text_with_image.py with 6 static-analysis checks
on the handler shape, matching the pattern of test_issue1095_pasted_images.py.
2026-05-04 10:48:36 -04:00
Frank Song
3f56ed7283 Polish forked session indicator 2026-05-04 21:50:40 +08:00
Frank Song
26208e46ae fix(server): clean up dead socket code and fix macOS keepalive (closes #1583)
- Delete QuietHTTPServer.server_bind() override entirely:
  TCP_KEEP* setsockopts on the listening socket are no-ops without
  SO_KEEPALIVE, and SO_REUSEADDR=1 is already set by the parent class.
  The actual fix lives entirely in Handler.setup().

- Restructure Handler.setup() with per-platform branches so
  SO_KEEPALIVE=1 is always applied before timing params, and macOS
  (TCP_KEEPALIVE) gets keepalive instead of aborting on TCP_KEEPIDLE.
2026-05-04 16:35:42 +08:00
Frank Song
cdcd6021cc fix(cron): tighten worker-side broad-except in _run_cron_tracked (closes #1578)
Remove the try/except Exception wrapper around
cron_profile_context_for_home(...).__enter__() in _run_cron_tracked.
A silent fallback to ctx=None would leave the worker thread unpinned
against process-global HERMES_HOME, silently corrupting cross-profile
state — the same class of bug as #1573.

Add regression test to catch any future re-introduction.
2026-05-04 16:28:33 +08:00
Manfred
0b7f60a714 fix: show update branches in banner labels 2026-05-04 09:46:45 +02:00
Manfred
3c93d5a702 fix: keep cross-source continuations separate in sidebar 2026-05-04 09:30:47 +02:00
Manfred
93251e5bcb fix: preserve git remote names in update links 2026-05-04 09:30:47 +02:00
Michael Lam
e9d7d5e427 fix: keep frontend routes under subpath mounts 2026-05-04 00:06:58 -07:00
Michael Lam
032b680e26 fix: render streaming markdown on subpath mounts 2026-05-03 23:55:45 -07:00
Sanjay Santhanam
14fac05dc9 fix(streaming): use truthy-check for _pending_started_at fallback
Switch the per-turn duration fallback from `is not None` to a truthy check so
None, missing-attr, and an explicit 0 all uniformly fall back to time.time().

Without this, a 0 timestamp (e.g. via a buggy migration or manual file edit)
would yield `time.time() - 0` ≈ wall-clock-since-epoch, displaying nonsense
like 'Done in 56 years 4 months ...'. In practice pending_started_at is always
set via int(time.time()) so this is a hardening fix, not a live-bug fix.

Also drop the brittle source-string assertion in the regression test that
pinned the literal expression. The behavioural test
test_done_handler_persists_duration_on_last_assistant_message already proves
the duration field is set; pinning the source line broke twice during the
v0.50.290 release pipeline alone (Opus tightening + maintainer revert).

Fixes #1595

Signed-off-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
2026-05-03 23:21:19 -07:00
Michael Lam
22187d2b4c fix: resolve provider config cleanup path 2026-05-03 23:13:10 -07:00
Michael Lam
ad46d82060 fix: isolate pytest Hermes config path 2026-05-03 22:47:55 -07:00
Michael Lam
6c5bc95b3b fix: broadcast SSE events to all tabs 2026-05-03 22:43:11 -07:00
nesquena-hermes
9986d2fd30
Merge pull request #1596 from nesquena/stage-291
Release v0.50.291 — 'What's new?' link 404 fix (closes #1579)
2026-05-03 22:32:35 -07:00
test
7e8249e6f8 Stage 291: PR #1594 — 'What's new?' link 404 fix via merge-base (closes #1579) by @nesquena-hermes — APPROVED 2026-05-04 05:30:27 +00:00
nesquena-hermes
3369a08f37 fix(updates): use merge-base for compare URL so 'What's new?' link resolves
Closes #1579.

api/updates.py was building the GitHub compare URL from local HEAD short SHA:

    repoUrl + '/compare/' + curSha + '...' + newSha
    where curSha = `git rev-parse --short HEAD`

Whenever local HEAD diverges from upstream — unpushed work, dirty stage
branches, forks, in-flight rebases, release-time merge commits whose SHA
only lives in the maintainer's local history — the compare URL points at
a SHA github.com has never seen and returns the standard 404 page.

Reporter (@ai-ag2026) observed:
  c660c7f...86cb22e
  → 404 because c660c7f was an unpushed local commit.

The right base is `git merge-base HEAD <compare_ref>` — the most recent
commit local and upstream share. Since `git fetch` succeeded just before,
the merge-base is guaranteed to exist on the upstream GitHub repo.

Behavior matrix:
  Pure-behind clone (no local commits): merge-base == HEAD; URL unchanged.
  Behind + local-only commits (#1579):  merge-base != HEAD; URL points at
                                        public ancestor instead of local HEAD.
  merge-base failure (shallow clone):   current_sha=None; JS link guard
                                        suppresses link rather than emitting
                                        a known-broken URL.

Also hardens static/ui.js: reset the link's href and display:none on every
banner render, so a stale link from a prior render can't survive a re-render
where the new payload has current_sha=null.

Tests:
  - test_current_sha_is_merge_base_not_local_HEAD — reporter's scenario
  - test_current_sha_equals_HEAD_when_no_local_commits — backward compat
  - test_current_sha_falls_back_to_None_when_merge_base_fails — defensive
  - test_whats_new_link_resets_display_and_href_on_every_render
  - test_whats_new_link_suppressed_when_curSha_falsy
  - test_reporter_url_shape_no_longer_produces_invalid_compare_url

4094 → 4100 passing. 0 regressions.
2026-05-04 05:26:19 +00:00
nesquena-hermes
45591638a9
Merge pull request #1593 from nesquena/stage-290
Release v0.50.290 — 5-PR batch (login cache + sidebar UX + workspace dropdown polish)
2026-05-03 22:12:22 -07:00
Hermes Release Agent
1636ab9ef9 release: stamp v0.50.290 — 5-PR batch (#1586+#1590+#1591+#1592+#1464) — 4094→4111 tests
- #1586 (Michaelyklam): login asset SW cache exemption
- #1590 (Michaelyklam): hot-apply compact tool activity setting
- #1591 (Michaelyklam): first-turn sidebar visibility (optimistic upserts)
- #1592 (Michaelyklam): turn duration display (Done in 1m 12s) + Opus follow-up (truthy-check on _pending_started_at)
- #1464 (JKJameson, maintainer-augmented): workspace dropdown sort+search+chip-sync (rebased + ternary fix + regression test)

Maintainer-side test fixes in stage:
- tests/test_465_session_branching.py: widen compact() search window 1500→3000
- tests/test_regressions.py: anchor on api('/api/chat/start' instead of comment line

Browser API sanity: 11/11 passed. Live UX verification: vision-confirmed dropdown sort+search+empty-state on test server. Opus advisor: SHIP AS-IS.
2026-05-04 05:10:29 +00:00
Hermes Bot
47d1a29ead Stage 290: PR #1464 — workspace dropdown sort+search+chip-sync by @JKJameson (maintainer-augmented: ternary fix + regression test) 2026-05-04 04:51:43 +00:00
Hermes Bot
d15b0a2929 Stage 290: PR #1592 — turn duration display 'Done in 1m 12s' by @Michaelyklam 2026-05-04 04:51:43 +00:00
Hermes Bot
38a9878821 Stage 290: PR #1591 — first-turn sidebar visibility (optimistic upsert) by @Michaelyklam 2026-05-04 04:51:43 +00:00
Hermes Bot
84429b2298 Stage 290: PR #1590 — hot-apply compact tool activity setting by @Michaelyklam 2026-05-04 04:51:43 +00:00
Hermes Bot
c87aebf68d Stage 290: PR #1586 — login asset SW cache exemption (closes auth-stuck-in-cache class) by @Michaelyklam 2026-05-04 04:51:42 +00:00
Josh
4174a7a860 fix: immediate syncTopbar on chat switch + sortable searchable workspace dropdown
Co-authored-by: Josh Jameson <josh@jjameson.com>

Maintainer-augmented:
- Flip noResults ternary (visible?'none':'' instead of visible?'':'none') —
  the contributor's first-push bug rendered 'No workspaces found' alongside
  valid filtered results. Verified on contributor's own screenshot in PR.
- Add tests/test_issue1464_workspace_dropdown_filter.py to lock the
  visibility relationship (mirror-image opt/noResults ternaries) so future
  edits cannot silently re-invert.
- Rebased onto master (was 124 commits behind v0.50.275).
2026-05-04 04:51:30 +00:00
Michael Lam
3afa23ecb7 fix: clear first-turn sidebar spinner on start failure 2026-05-03 21:14:21 -07:00
Michael Lam
0eddb0580e fix: document turn duration fallback 2026-05-03 21:12:07 -07:00