Cherry-picked via 3-way apply onto stage HEAD (post-Release-A/B/C1).
Resolved boot.js conflict: took PR's parameterized
populateModelDropdown({preferProfileDefaultOnFreshBoot:true}) call
(the whole point of #2726) on top of master's #2716 boot path.
Co-authored-by: starship-s <starship-s@github.users.noreply.github.com>
Opus pre-release advisor MUST-FIX patched inline:
- api/routes.py:7290-7308 _handle_folder_download: add Connection: close
header before end_headers() to satisfy HTTP/1.1 framing on the on-the-fly
ZIP stream. Without it, post-#2836 protocol_version bump leaves clients
hanging waiting for the next pipelined response after central-directory
bytes finish. Opus verified this is the ONLY streaming response #2836
missed — all other paths (j/t helpers, 12 hand-written responses, 8 SSE
endpoints, auth flow) are already correctly framed.
- Add Turkish translations for 16 settings_aux_* / settings_label/desc_auxiliary_models
keys that #2680 added against the 10-locale set (pre-#2772 Turkish baseline).
- Bump test_auxiliary_models_settings.py::test_all_locales_have_auxiliary_keys
from count == 11 to count == 12 (one per locale, now including tr).
Cherry-picked via 3-way apply of net delta against stage HEAD. All 8 files
applied cleanly including the new static/pwa-startup.js.
Co-authored-by: AJV20 <abdielvc@me.com>
Cherry-picked via 3-way apply (rebase had failed on static/index.html
conflict when applied via rebase commit chain; 3-way of the net delta
against stage HEAD applied cleanly).
Co-authored-by: mccxj <mccxj@github.users.noreply.github.com>
Agent reviewer 'LGTM. Ship it.'
- Bug A fix: _session_field helper handles dict-vs-object snapshot in pin-limit check
- Bug B fix: removed stale client-side pinLimitReached short-circuit
- Bug C recovery: renderSessionList() on pin/unpin failure refreshes from server
Co-authored-by: franksong2702 <146128127+franksong2702@users.noreply.github.com>
nesquena APPROVED 2026-05-22. Cherry-picked onto post-v0.51.127
master via 3-way apply. Resolved api/routes.py conflict: master had
the inline correctness fix from the deep-review iteration; PR
refactors it into _metadata_only_message_summary() helper. Took the
helper AND added profile= threading (post-#2827 master adds
profile-aware state.db reads). Kept master's pre-existing
test_api_session_reload_drops_stale_cached_user_tail_after_saved_assistant
alongside the PR's new test_metadata_fast_path_matches_reconciliation_for_restamped_replays.
Co-authored-by: dobby-d-elf <dobby.the.agent@gmail.com>
MUST-FIX:
- tests/test_2735_open_in_vscode.py: bump expected open_in_vscode locale
counter from 10 to 11 (Turkish locale added in #2772). The bump fell
out of an in-rebase test edit but never got committed; tagging without
this would have shipped a failing test in the release commit.
SHOULD-FIX inline:
- api/updates.py: case-D drift in _select_apply_compare_ref. The original
#2855 fix used latest_tag in the past-tag predicate; the check side
uses current_tag (HEAD's nearest reachable tag) plus a 'behind == 0'
gate. They drift when HEAD is on an OLDER release tag with commits on
top AND a NEWER tag exists ('case D'): check correctly suggests
advancing to the newer tag, but apply fell through to origin/<branch>.
Mirror the check-side predicate exactly. Adds regression test
test_select_apply_compare_ref_case_d_older_tag_with_commits_and_newer_tag_exists.
- static/messages.js: post-await race guard in _restoreSettledSession.
stream_end without preceding 'done' enters the settlement path, awaits
/api/session, then sets _streamFinalized=true. If a late 'done' event
arrives during that await, it sees _streamFinalized still false and
double-runs the finalize. The guard returns early when done won the
race, avoiding double renderMessages() + double notification.
- server.py: CORS preflight Access-Control-Allow-Methods now includes PUT.
#2776 wired PUT into the router for /api/mcp/servers/{name} but didn't
update the OPTIONS response. Same-origin only in practice, but cosmetic
completeness for CORS-aware deployments.
Opus advisor verdict: all 5 risk areas reviewed, 1 MUST-FIX + 3 SHOULD-FIX
all addressed inline. Net: +69/-9, no new architecture, no behavior risk.
Replace \\u2026 with \u2026 (and fix \\u2192/\\u2713) in the tr block
so ellipsis renders as U+2026 instead of literal backslash-u text.
Add a regression test guarding against double-escaped unicode sequences.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a complete Turkish locale to the WebUI and login page so users can
select Türkçe in Settings, with speech recognition via tr-TR.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add `PATCH /api/mcp/servers/{name}` endpoint that accepts `{"enabled": bool}`,
updates `mcp_servers.<name>.enabled` in config.yaml, and calls `reload_config()`.
Mirrors the existing DELETE pattern.
Also wire the previously-defined-but-unrouted `_handle_mcp_server_delete` into
`handle_delete`, and `_handle_mcp_server_update` into a new `handle_put` +
`do_PUT` in server.py — fixing a pre-existing bug where those handlers existed
but were never reachable over HTTP.
UI: add a toggle button in each MCP server row in the system settings panel
(panels.js). Clicking it calls PATCH and reloads the list. Toggle button is
styled with `.mcp-toggle-enabled` / `.mcp-toggle-disabled` CSS classes. The
`toggle_supported` flag in the list response is now `True`.
i18n: add 5 new keys (`mcp_enable_server`, `mcp_disable_server`,
`mcp_enabled_toast`, `mcp_disabled_toast`, `mcp_toggle_failed`) to all 9
non-English locales (English values as placeholder translations).
Tests: add `TestMcpToggle` class with 7 tests covering disable, enable,
404-not-found, empty name, missing field, response payload, and URL-encoded name.
Update `test_empty_config` and visibility panel assertions to reflect
`toggle_supported: True` and the new toggle button in panels.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes#2846. After PR #2758 (the #2653 fix) the update check correctly
falls through to the branch comparison when HEAD has moved past the
latest `v*` tag — so the banner reports the real commit count against
`origin/<branch>`. But `_select_apply_compare_ref` was never updated to
mirror that decision: as long as any `v*` tag exists, it returns
`tags[0]`, even when HEAD is far past it.
Result for everyone running hermes-agent past `v2026.5.16` (i.e. anyone
on agent master between tagged releases):
1. Banner: `Agent (origin/main): 254 updates available` ← correct
2. User clicks Update Now
3. `_select_apply_compare_ref` picks `v2026.5.16` because tags exist
4. `git pull --ff-only origin v2026.5.16` — no-op (HEAD is already past it)
5. `_schedule_restart()` fires anyway, server bounces
6. Next check still reports 254 behind — banner reappears unchanged
`apply_force_update` had the same bug, except worse: `git reset --hard
v2026.5.16` would have actively rewound the user's checkout 254 commits.
The root cause is the same bug class as #2653 — two parallel paths
(`_check_repo_release` and `_select_apply_compare_ref`) that should make
the same decision but didn't. Pre-fix, the "is HEAD past the latest
tag?" predicate lived inline inside `_check_repo_release` only.
Fix
---
Extract `_head_is_past_latest_tag(path, current_tag)` and have both
paths consult it. When HEAD is past the latest tag:
- check path: release check returns None → branch check runs (#2653,
unchanged behaviour, just refactored)
- apply path: falls through to upstream / `origin/<branch>`, never the
stale tag (#2846, new behaviour)
Tests
-----
- `test_select_apply_compare_ref_uses_tag_when_head_is_on_tag` —
unchanged behaviour pinned: HEAD exactly on tag → advance to tag.
- `test_select_apply_compare_ref_falls_through_when_head_is_past_tag` —
the #2846 repro: HEAD = v2026.5.16 + 608 commits → advance to
`origin/main`, not the tag.
- `test_select_apply_compare_ref_no_tags_uses_upstream` — unchanged.
- `test_select_apply_compare_ref_no_tags_no_upstream_uses_default_branch`
— unchanged.
- `test_check_and_apply_paths_agree_when_head_is_past_tag` — symmetry
test, ensures the two paths can't drift apart again.
All 21 tests in `tests/test_updates.py` pass locally (16 existing + 5
new).
Refs #2846, #2653.
Fixes#2853. The `_terminal_shell_preexec_fn` added in `71d8a8fb` called
`prctl(PR_SET_PDEATHSIG, SIGTERM)` so orphaned PTY shells would die when
the WebUI process crashed. But that signal is **per-thread**, not
per-process, and WebUI runs `ThreadingHTTPServer`: every HTTP request is
handled in its own short-lived worker thread.
Flow that broke every Linux user:
1. User clicks the terminal toggle → frontend hits `POST /api/terminal/start`.
2. ThreadingHTTPServer spins up a worker thread to handle that one request.
3. The worker thread calls `subprocess.Popen(..., preexec_fn=...)`.
4. The shell calls `prctl(PR_SET_PDEATHSIG, SIGTERM)` in its preexec_fn.
Its registered "parent" is now the WebUI worker thread that called Popen.
5. The handler returns its JSON response and the worker thread exits.
6. The kernel sees the pdeathsig-parent thread has died and sends SIGTERM
to the PTY shell. The shell dies within ~10 ms of being created.
7. The reader loop sees EIO on the master FD, emits `terminal_closed`, and
the frontend writes `[terminal closed]`.
macOS users were unaffected because `libc.prctl` doesn't exist there —
`ctypes.CDLL(None)` returns a libc handle, `libc.prctl` raises
`AttributeError`, the bare-`except` swallows it, and the shell starts
with no pdeathsig configured.
Empirical verification on this Linux host (real PTY + `subprocess.Popen`
inside a `threading.Thread` that joins immediately):
with preexec_fn → proc.poll() == -15 (SIGTERM), master FD returns EIO
without preexec_fn → proc.poll() == None (alive), master FD returns "HELLO\\r\\n"
Same shell, same PTY, same threading topology as WebUI.
Fix
---
Drop the `preexec_fn` entirely. The orphan-shell-on-crash case the original
PR was navigating is rare for self-hosted single-user installs, and the
existing `atexit.register(close_all_terminals)` + explicit `close_terminal`
paths cover graceful shutdown. A future fix (option B in the issue) can
re-introduce pdeathsig pinned to a long-lived supervisor thread, but that
is a follow-up — this PR is the smallest unbricks-Linux-today change.
Tests
-----
- Invert `test_terminal_shell_uses_parent_death_signal_preexec` →
`test_terminal_shell_does_not_use_pdeathsig_preexec`: asserts
`preexec_fn` is NOT in the Popen kwargs.
- Add `test_pty_shell_survives_when_spawning_thread_exits`: spawns a
real PTY shell via `start_terminal` from a worker thread, waits for
the worker to join, asserts the shell is still alive after a half-second
grace window. This is the contract the original tests never exercised.
- Update `test_terminal_module_registers_graceful_shutdown_reaper` to
refuse re-introduction of the preexec_fn or the `libc.prctl(1, SIGTERM)`
call (treats either as a regression).
All 27 terminal-related tests pass locally.
Refs #2853
Three small fixes from Copilot's review:
1. static/style.css:1354 - removed spaces inside `clamp(...)` args to
match the file's existing compact style (no spaces after commas in
neighboring declarations like `transition:border-color .2s,box-shadow .2s`).
2. CHANGELOG.md - wrapped the long single-line entry across multiple lines
with standard Markdown continuation indentation for cleaner diffs.
3. CHANGELOG.md - normalized `~1300 px` to `~1300px` for unit-formatting
consistency.
No behavior change. Same one-line CSS rule, just tightened formatting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.composer-box` had a hardcoded `max-width: 780px` since the early
v0.50.x layout pass. On wide displays (1440p+, 2880px ultrawides)
this leaves significant unused horizontal space AND squeezes the
composer-footer chips (workspace, model, reasoning, context %)
against each other inside the 780px box.
When the context-percentage ring appears (active token usage), the
workspace chip truncates to "Fou..." instead of showing the full
workspace name. Model + reasoning chips also lose room. The chip
strip horizontally-scrolls inside .composer-left, so the rightmost
chips effectively hide behind context %.
The constraint isn't "Reading flow looks better at 780px" — the
textarea is min-height:64px, max-height:200px and wraps naturally,
so users on wide displays get the SAME readable text wrap regardless
of box width. Only the footer chips suffer.
Fix: clamp(780px, 60vw, 1100px). Preserves the 780px floor (no
regression on viewports < 780px since clamp's first arg is the
minimum) while letting wider viewports use up to 1100px (60% of
viewport width, capped). 1100px gives ~40% more horizontal room for
the footer chips without filling the entire screen at extreme widths.
Per-viewport behavior:
<= 780 px → 780 px (hard floor) — zero change vs current
1280 px → 60vw = 768 → floored to 780 — zero change
1440 px → 60vw = 864 — +84 px room
1920 px → 60vw = 1152 → capped at 1100 — +320 px room
2880 px → 60vw = 1728 → capped at 1100 — +320 px room
One line in static/style.css. CHANGELOG entry. No JS. No new deps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The path-discovery step succeeds on the first run, but the cleanup
step exits non-zero because `taskkill /PID 5560 /T /F` returns 128
("process not found") when server.py has already exited on the mock
hermes_cli stub. That's the expected steady state for this mock-only
workflow, not a failure.
Two-line fix: reset `$global:LASTEXITCODE = 0` after the taskkill
call, and explicit `exit 0` at the end of the step so any other
external-command exit codes don't bubble up. The try/catch wrapper
didn't help because taskkill writes its diagnostic to stderr without
raising a PowerShell exception — `catch` never fired.
Run 26352805510 on this branch shows the failure shape: "OK: start.ps1
path discovery - all guards passed." in the verify step, then
"ERROR: The process '5560' not found." in the cleanup step. Path
discovery is what this workflow exists to validate; cleanup just has
to not fail the job.
Per @nesquena-hermes review on #2811: hermes-agent isn't published to
PyPI, so `pip install hermes-agent` finds nothing and start.ps1's
hermes_cli guard correctly bails out — leaving the previous workflow
unable to self-validate against release/stage-batch6.
This rework adopts option 1 from the review: drop the pip install,
stub a hermes_cli/ directory with a minimal __init__.py next to the
sibling hermes-agent/ folder, then run start.ps1 for 8 seconds and
assert that none of its own Write-Error guards (no Python, no agent
dir, bad port, missing hermes_cli, missing server.py) appeared in
stderr. /health is no longer probed — the server cannot boot on a
stub, and full-boot regressions stay covered by the Linux jobs and
docker-smoke.yml.
Scope intentionally narrower than the original: this workflow
validates start.ps1's PowerShell syntax + path discovery only. The
exact bug class PR #2805 caught (WOW64 ProgramFiles redirect) would
now light up red here pre-merge, which is the reason this gate exists.
Paths filter trimmed to `start.ps1` + the workflow itself; the broader
list (requirements.txt / bootstrap.py / server.py) was inherited from
the original full-boot scoping and isn't relevant for a path-discovery-
only run.
Verification: workflow runs on this PR via its own pull_request trigger.
The first CI run on this branch IS the verification.
CHANGELOG updated under [Unreleased] with a single bullet sized to the
surrounding density.
Squashed from 2 author commits:
- d2237e23 feat: surface live activity timeline
- eee57ec0 fix: satisfy activity timeline CI guards
Frontend-only telemetry from existing stream events. Replaces empty
Thinking… placeholder with observable run status (Waiting on model /
Waiting on tool result / Working for …). New CSS, new test file.
- Rewrote _kanbanRenderMarkdown() from basic paragraph wrapper to a
line-by-line block processor supporting headings, code blocks, lists,
task lists, tables, blockquotes, horizontal rules, and strikethrough.
- Added CSS for all new elements (table borders, code blocks, checkboxes,
blockquote accent, heading sizing, etc.).
- Dropped white-space: pre-wrap from .kanban-task-preview-body and
.kanban-detail-row-main since markdown now handles layout.
- Applied _kanbanRenderMarkdown() to task description (was esc()) and
comment body (was esc()) in the task detail view.