diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2f299..27ca1fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Hermes Web UI -- Changelog +## [v0.50.69] — 2026-04-16 + +### Fixed +- **Docker: workspace file browser no longer appears empty on macOS** — `docker_init.bash` now auto-detects the correct `WANTED_UID` and `WANTED_GID` from the mounted `/workspace` directory at startup. On macOS, host UIDs start at 501 (not 1000), so the default value of 1024 caused the container user to run as a different UID than the files, making the workspace appear empty. The auto-detect reads `stat -c '%u'` on `/workspace` and uses it when no explicit `WANTED_UID` is set — falling back to 1024 if the path doesn't exist or returns 0 (root). Setting `WANTED_UID` explicitly in a `.env` file still takes full precedence. (Closes #569) +- **Session message count inconsistency resolved** — the topbar already correctly shows only visible messages (excluding `role='tool'` tool-call entries). The sidebar previously showed raw `message_count` which included tool messages, but PR #584 removed that display entirely — there is no longer any count displayed in the sidebar. No code change needed; documenting with regression tests. (Closes #579) + ## [v0.50.68] — 2026-04-16 ### Fixed diff --git a/docker_init.bash b/docker_init.bash index edd685e..0b07de4 100644 --- a/docker_init.bash +++ b/docker_init.bash @@ -59,6 +59,20 @@ it=$itdir/hermeswebui_user_uid if [ -z "${WANTED_UID+x}" ]; then if [ -f $it ]; then WANTED_UID=$(cat $it); fi fi +# Auto-detect from mounted workspace if still unset (#569). +# On macOS, host UIDs start at 501. Using the wrong UID means the container +# user cannot read the bind-mounted files, making the workspace appear empty. +# Prefer the workspace mount UID over the hardcoded default of 1024. +if [ -z "${WANTED_UID+x}" ] || [ "${WANTED_UID}" = "1024" ]; then + # Use /workspace — the standard bind-mount point — to read the host UID. + if [ -d "/workspace" ]; then + _detected_uid=$(stat -c '%u' "/workspace" 2>/dev/null || echo "") + if [ -n "$_detected_uid" ] && [ "$_detected_uid" != "0" ]; then + echo "-- Auto-detected workspace UID: $_detected_uid (from /workspace)" + WANTED_UID=$_detected_uid + fi + fi +fi WANTED_UID=${WANTED_UID:-1024} write_worldtmpfile $it "$WANTED_UID" echo "-- WANTED_UID: \"${WANTED_UID}\"" @@ -67,6 +81,16 @@ it=$itdir/hermeswebui_user_gid if [ -z "${WANTED_GID+x}" ]; then if [ -f $it ]; then WANTED_GID=$(cat $it); fi fi +# Auto-detect GID from mounted workspace to match (#569) +if [ -z "${WANTED_GID+x}" ] || [ "${WANTED_GID}" = "1024" ]; then + if [ -d "/workspace" ]; then + _detected_gid=$(stat -c '%g' "/workspace" 2>/dev/null || echo "") + if [ -n "$_detected_gid" ] && [ "$_detected_gid" != "0" ]; then + echo "-- Auto-detected workspace GID: $_detected_gid (from /workspace)" + WANTED_GID=$_detected_gid + fi + fi +fi WANTED_GID=${WANTED_GID:-1024} write_worldtmpfile $it "$WANTED_GID" echo "-- WANTED_GID: \"${WANTED_GID}\"" diff --git a/static/index.html b/static/index.html index 1c5b1a3..acacf13 100644 --- a/static/index.html +++ b/static/index.html @@ -555,7 +555,7 @@
System
Instance version and access controls.
- v0.50.68 + v0.50.69
diff --git a/tests/test_issue569_579.py b/tests/test_issue569_579.py new file mode 100644 index 0000000..d0e50b5 --- /dev/null +++ b/tests/test_issue569_579.py @@ -0,0 +1,105 @@ +""" +Tests for fixes: + +- #569: docker_init.bash auto-detects WANTED_UID/WANTED_GID from mounted workspace + so macOS users (UID 501) don't need to manually set the env var. +- #579: Topbar message count already filters tool messages (role !== 'tool') — + confirmed present. Closing as already fixed by #584 which removed the + sidebar meta row (the only place raw message_count was ever displayed). +""" +import pathlib +import re + +REPO_ROOT = pathlib.Path(__file__).parent.parent +INIT_SH = (REPO_ROOT / "docker_init.bash").read_text(encoding="utf-8") +UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") + + +# ── #569: docker UID/GID auto-detect ───────────────────────────────────────── + +def test_569_uid_autodetect_present(): + """docker_init.bash must have workspace-based UID auto-detection (#569).""" + assert "stat -c '%u'" in INIT_SH or 'stat -c \'%u\'' in INIT_SH, ( + "docker_init.bash must use stat to read workspace UID (#569)" + ) + + +def test_569_gid_autodetect_present(): + """docker_init.bash must have workspace-based GID auto-detection (#569).""" + assert "stat -c '%g'" in INIT_SH or 'stat -c \'%g\'' in INIT_SH, ( + "docker_init.bash must use stat to read workspace GID (#569)" + ) + + +def test_569_autodetect_before_usermod(): + """UID auto-detect must appear before usermod call in docker_init.bash.""" + detect_pos = INIT_SH.find("stat -c '%u'") + if detect_pos == -1: + detect_pos = INIT_SH.find("stat -c") + usermod_pos = INIT_SH.find("sudo usermod") + assert detect_pos != -1, "stat UID detection not found" + assert usermod_pos != -1, "sudo usermod not found" + assert detect_pos < usermod_pos, ( + "UID auto-detect must occur before 'sudo usermod' so the correct UID " + "is used when remapping the hermeswebui user" + ) + + +def test_569_skips_root_uid(): + """Auto-detect must not use UID 0 (root-owned mount = untrustworthy).""" + detect_block_start = INIT_SH.find("Auto-detect from mounted workspace") + assert detect_block_start != -1, "auto-detect comment block not found" + block = INIT_SH[detect_block_start:detect_block_start + 600] + assert '"0"' in block or "'0'" in block, ( + "Auto-detect block must skip UID 0 to avoid incorrectly using root ownership" + ) + + +def test_569_fallback_preserved(): + """Hardcoded default 1024 fallback must still exist after auto-detect.""" + assert "WANTED_UID=${WANTED_UID:-1024}" in INIT_SH, ( + "WANTED_UID default fallback must remain so explicit env var still works" + ) + assert "WANTED_GID=${WANTED_GID:-1024}" in INIT_SH, ( + "WANTED_GID default fallback must remain" + ) + + +# ── #579: topbar message count already filters tool messages ────────────────── + +def test_579_topbar_filters_tool_messages(): + """ui.js topbar count must filter out role='tool' messages (#579). + + The sidebar previously showed raw message_count (which included tool + messages), causing a mismatch with the topbar. PR #584 removed the + sidebar count display entirely; the topbar was already correct. + This test locks in the existing topbar filter so it can't regress. + """ + # Find the topbarMeta assignment + meta_pos = UI_JS.find("topbarMeta") + assert meta_pos != -1, "topbarMeta assignment not found in ui.js" + + # Find the filter that precedes it — should exclude role==='tool' + context = UI_JS[max(0, meta_pos - 400):meta_pos + 100] + assert "role" in context and "tool" in context, ( + "topbarMeta count must filter by role — " + "messages with role='tool' must be excluded from the displayed count" + ) + # The filter must exclude tool messages (not include them) + assert "!=='tool'" in context or "!= 'tool'" in context or "role!=='tool'" in context, ( + "topbar count filter must use !== 'tool' to exclude tool messages" + ) + + +def test_579_sidebar_no_longer_shows_raw_count(): + """sessions.js must not reference message_count in the render path (#579). + + After PR #584, the sidebar no longer shows message_count at all, + eliminating the inconsistency between sidebar (raw) and topbar (filtered). + """ + sessions_js = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8") + # message_count should not appear in the client-side session renderer + assert "message_count" not in sessions_js, ( + "sessions.js must not reference message_count — " + "the meta row that displayed it was removed in PR #584" + )