Commit Graph
7575 Commits
Author SHA1 Message Date
nesquena-hermes 7e091e09dd Merge pull request #6488 from happy5318/fix/qwen-prefixed-reasoning-heuristic
fix: Qwen reasoning heuristic fails for prefixed model IDs
2026-07-29 11:12:13 -07:00
happy5318 a57d576805 fix: make prefixed Qwen detection positive-only to avoid shadowing hybrid IDs
Gate review found that the broadened token-prefix check caused
deepseek-r1-distill-qwen2.5-bakeneko-32b to enter the Qwen branch
(qwen2 token), return False, and prevent the DeepSeek detector from
running — a silent regression.

Fix: split into two phases:
1. Positive-only scan: any token matching qwen(\d+) with major >= 3
   immediately returns True. No False path — Qwen 2.x in hybrid IDs
   falls through to subsequent detectors.
2. Terminal guard (original behavior): standalone "qwen" token or
   normalized.startswith("qwen") still applies the 3+ gate with
   return False for 2.x, since those ARE pure Qwen models.

Add regression test for deepseek-r1-distill-qwen2.5-bakeneko-32b.
2026-07-29 18:07:55 +00:00
happy5318 90250adfb7 fix: address review — remove redundant guard, add regression tests
- Drop the now-subsumed `normalized.startswith("qwen")` guard
  (token-prefix matching already covers the bare case)
- Add test_qwen_prefixed_alias_reasoning_detection covering:
  prefixed Qwen 3+ (al-/sn-), bare Qwen 3+, and Qwen 2.x exclusion
2026-07-29 18:07:55 +00:00
happy5318 041e311f1e fix: Qwen reasoning heuristic fails for prefixed model IDs
The Qwen branch in _candidate_supports_reasoning() checks
"qwen" in token_set, but model IDs like "al-qwen3.8-max-preview"
(New-API alias prefixes) normalize to tokens ["al", "qwen3", "8", ...]
where "qwen" is not a standalone token. This causes the reasoning
effort toggle to disappear for any prefixed Qwen model.

Switch to token-prefix matching (token.startswith("qwen")), consistent
with the position-independent approach already used for DeepSeek
(tokens.index). This handles arbitrary vendor/alias prefixes while
still correctly excluding Qwen 2.x via the existing version gate.
2026-07-29 18:07:55 +00:00
nesquena-hermes 4b3fdde31b Merge pull request #6567 from stefanpieter/fix/sigusr1-stack-dump-no-restart
fix: keep SIGUSR1 stack dumps non-terminating
2026-07-29 10:53:29 -07:00
StefanPieter 8db75f8b78 fix: keep SIGUSR1 stack dumps non-terminating 2026-07-29 17:41:46 +00:00
starship-s d00c3332e4 ci: constrain MCP SDK to compatible 1.x releases (#6564)
Assisted-by: Hermes Agent:gpt-5.6-sol
2026-07-29 10:35:23 -07:00
nesquena-hermes 21bdd7c1f1 Merge pull request #6538 from nesquena/gate-6529-title-echo
Release exp-v0.52.153: reject trivial-echo session titles (#6529)
2026-07-26 13:27:24 -07:00
nesquena-hermes 4e1a217670 changelog: reject trivial-echo session titles (#6529) 2026-07-26 20:23:01 +00:00
nesquena-hermesandHermes Agent b145f59312 fix(titles): reject trivial-echo new session titles (gate-rebase of #6529)
Co-authored-by: Hermes Agent <hermes@nousresearch.com>
2026-07-26 20:22:25 +00:00
nesquena-hermes 8e3bb02e0e Merge pull request #6537 from nesquena/gate-6527-sse-relay-close
Release exp-v0.52.152: SSE relay apperror-hang fix (#6527)
2026-07-26 13:20:11 -07:00
nesquena-hermes 10223e3145 changelog: SSE apperror relay-close fix (#6527) 2026-07-26 20:15:14 +00:00
nesquena-hermes c6e9ad79d2 test: update legacy SSE-break regression test for SSE_RELAY_CLOSE_EVENTS (#6527 maintainer fold-in)
The R5b static-pattern test pinned the old inline ('stream_end','error','cancel')
tuple; #6527 replaced it with the named SSE_RELAY_CLOSE_EVENTS frozenset. Accept
either shape and assert behavior (cancel+apperror in the resolved close set)
rather than source syntax. Intent-preserving; non-vacuous (fails on master:
SSE_RELAY_CLOSE_EVENTS absent + apperror not in old tuple).
2026-07-26 19:58:43 +00:00
nesquena-hermesandHermes Agent c49568101c fix(sse): close relays on apperror terminal events (gate-rebase of #6527)
Co-authored-by: Hermes Agent <hermes@nousresearch.com>
2026-07-26 19:45:38 +00:00
nesquena-hermes a7180a8108 Merge pull request #6523 from nesquena/release/stage-6477
Release exp-v0.52.151: repair admin model reload rebinding (#6477, @mspgle-dotcom)
2026-07-25 19:13:28 -07:00
nesquena-hermes 20972409e3 docs(changelog): repair admin model reload rebinding (#6477, @mspgle-dotcom) 2026-07-26 02:08:45 +00:00
nesquena-hermes 44877274da Release exp-v0.52.151: repair admin model reload rebinding (#6477, @mspgle-dotcom) 2026-07-26 02:08:30 +00:00
nesquena-hermes dd155912a7 Merge pull request #6520 from nesquena/release/stage-5844
Release exp-v0.52.150: reap idle abandoned terminals (#5844, @ai-ag2026)
2026-07-25 18:03:47 -07:00
nesquena-hermes f8401f3be3 docs(changelog): reap idle abandoned terminals (#5844, @ai-ag2026) 2026-07-26 00:56:42 +00:00
nesquena-hermes 5662023b10 Release exp-v0.52.150: reap idle abandoned terminals (#5844, @ai-ag2026) 2026-07-26 00:56:18 +00:00
ai-ag2026 a31cdb7765 fix(terminal): unsubscribe when the SSE header write fails after attaching
Adversarial review of the previous commit found a leak it introduced.

`attach_terminal()` subscribes and only then are the response headers written.
A client that dropped in that instant makes `end_headers()` raise
BrokenPipeError/ConnectionResetError, and because the `try/finally` started
*after* those writes, the exception escaped `_handle_terminal_output` with the
queue still in `_subscribers`. That pins `unwatched_since` at None — only
`unsubscribe()` on an empty subscriber list resets it — so the terminal became
permanently unreapable: the exact fd/thread leak (#4633) this reaper exists to
prevent, and strictly worse than the TOCTOU the previous commit fixed, since
that at least still allowed cleanup.

The try now starts immediately after the attach, so every exit path
unsubscribes. Regression test drives a handler whose `end_headers()` raises and
asserts the subscriber list is empty, `unwatched_since` is restored, and the
terminal is reapable again once the grace elapses. It fails against the
previous ordering.
2026-07-25 03:17:30 +02:00
ai-ag2026 07d110a064 fix(terminal): close the reaper's selection→reconnect→close TOCTOU
`_terminals_to_reap()` snapshots victims under `_LOCK` and then releases it,
but `subscribe()` clears `unwatched_since` and appends to `_subscribers` under
the terminal's own `_sub_lock`, which the selection pass never held. A viewer
reconnecting in that gap left object identity — all `close_terminal(expected=…)`
ever checked — still true, so the reaper killed a terminal that now had a live
viewer.

- `_claim_reap_victim()` re-establishes the *whole* selection predicate while
  holding `_LOCK` and the terminal's `_sub_lock`, and removes the registry
  entry in that same critical section: same object, no subscribers, and
  `unwatched_since` still set and still expired. A dead process is still
  reaped unconditionally — it cannot come back, and a viewer watching a corpse
  must not pin the entry forever.
- `attach_terminal()` does the registry lookup and the subscribe atomically
  under `_LOCK`, replacing `get_terminal()` + `term.subscribe()` in the SSE
  route. It takes the same two locks in the same order as the claim, so
  exactly one side wins: the viewer attaches (and the reap is refused), or the
  entry is already gone (and the route answers "terminal not running" before
  committing a 200, instead of streaming from a corpse). Registration is the
  authority — a registered-but-`closed` terminal still attaches so the viewer
  receives `terminal_closed`.
- Teardown moved to `_teardown_terminal()` and runs after both locks are
  released; `killpg`+`wait` can block ~2.5s and must not pin the registry.
- Dropped the duplicated `self.last_activity = time.time()` the rebase left in
  `put_output()`.

Tests: a barrier regression that pauses the reaper after selection, attaches a
viewer through the production path, resumes, and asserts the terminal stays
registered/live and still receives output; the inverse where the reap claims
first and the attach reports not-running; refreshed-grace and replaced-entry
claims; dead-with-viewer; and a probe that `_LOCK` is free during teardown.
Three of them fail against an identity-only claim.
2026-07-25 00:33:41 +02:00
ai-ag2026andClaude Fable 5 932e858994 feat(terminal): reap idle abandoned terminals so #4633 can't accumulate
The fd-leak fix retires a terminal whose shell EXITS, and _MAX_TERMINALS bounds
the worst case, but an interactive shell abandoned by its client (tab closed
without POSTing /api/terminal/close, browser crash, network drop) keeps running
forever — there is deliberately no PDEATHSIG. A background reaper now closes any
terminal that has had zero attached output streams for longer than a 15-minute
idle grace, plus any dead-process terminal as a belt-and-suspenders for the
reader-loop retire.

Attachment is tracked on the subscriber model from the output-broadcast change:
subscribe() clears unwatched_since, the last unsubscribe() sets it. A terminal
is born unwatched, so a spawn nobody ever attaches to is reaped too; the grace
spans a tab refresh / brief drop so a real reconnect keeps the session. Eviction
uses close_terminal(sid, expected=term) so a restart replacement is never torn
down. The reaper thread is a lazily-started daemon singleton like the spawn
supervisor.

Stacked on the terminal fd-leak + output-broadcast changes (same file).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 00:30:06 +02:00
ai-ag2026andClaude Fable 5 1603eb8611 fix(terminal): stop leaking fd + thread + shell on client/​shell exit
Two embedded-terminal resource leaks (#4633):

1. fd + dict entry on shell exit. When a shell exited on its own (user typed
   `exit`, the process died), _reader_loop broke out of its loop but only set
   `closed` and emitted terminal_closed — it never closed the pty master_fd nor
   removed the _TERMINALS entry. Only an explicit close_terminal / restart /
   atexit did that, so every self-exited shell leaked its master fd and dict
   entry for the rest of the WebUI's uptime. The reader loop now retires the
   session (close_terminal with an `expected=` identity guard so a restart's
   replacement is never torn down).

2. Unbounded accumulation of abandoned terminals. A client that drops its output
   stream without POSTing /api/terminal/close (tab close, crash, network drop)
   leaves the shell running (no PDEATHSIG, by design). _MAX_TERMINALS (32) now
   caps the live population, evicting a dead-proc terminal first, else the
   least-recently-active one, to make room.

Also hardens an fd-reuse TOCTOU that #1 would otherwise make reachable on the
common `exit` path: write_terminal / resize now re-check `closed` and touch
master_fd under a per-terminal io_lock that close_terminal also holds around
os.close, so input can never land on a closed-and-recycled fd number.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 00:30:05 +02:00
Hermes Agent b104abeae6 fix(admin): repair model reload rebinding 2026-07-24 18:05:17 +08:00
nesquena-hermesandnesquena-hermes 399cd7ab19 Release: credit collapsed wakeup cards (#6350, @b3nw) (#6466)
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-23 20:54:32 +00:00
b3nwandb3nw e93b0c352a feat: render process wakeups as collapsed summary cards (#6350)
Render structured background-process completions and watch matches as accessible collapsed cards while preserving raw fallback, verbatim output, and full watch patterns.

Co-authored-by: b3nw <b3nw@duck.com>
2026-07-23 20:52:51 +00:00
nesquena-hermes ecc47782e3 Merge pull request #6447 from nesquena/stage/6349
Release: composer keystroke resize round-trip fix (#6349)
2026-07-22 21:24:22 -07:00
nesquena-hermes 2bd05ee32b docs(changelog): #6349 composer keystroke resize round-trip fix 2026-07-23 04:15:46 +00:00
nesquena-hermesandsheldon-im 3157f4cbf2 harden(#6349): strict finite-px min-height parse for the composer fast path
Codex re-gate found the single-row resize fast path used parseFloat on
getComputedStyle(el).minHeight, which reads a bogus pixel value for a
non-px unit (parseFloat('50%')===50) and would wrongly enable the fast
path, leaving an oversized composer stuck tall. Parse with a strict
/^<number>px$/ test so %, auto, empty, and calc() fail closed to the
full resize. Adds red-before-green regression coverage across units.

Co-authored-by: sheldon-im <sheldon-im@users.noreply.github.com>
2026-07-23 04:05:03 +00:00
nesquena-hermes 77d0b8609d Merge remote-tracking branch 'origin/master' into stage/6349 2026-07-23 03:25:14 +00:00
nesquena-hermes d2a4ecb767 Merge pull request #6446 from nesquena/stage/6040
Release: gateway approvals routed by run ID (#6040, fix #6008)
2026-07-22 20:20:05 -07:00
nesquena-hermesandrodboev 575e45f76b polish(#6040): approval-counter CSS cleanup + i18n the pending-count string
Fable UX polish pass on the gateway approval counter:
- Move the crude inline style (font-size:0.75em;opacity:0.6;margin-top:4px) into
  a proper .approval-counter rule: 11px, color:var(--muted) (skin-safe vs opacity),
  margin:0 0 12px (fixes the 0px gap that left it flush against the buttons),
  tabular-nums for stable digits.
- i18n the '1 of N pending' string (was the only hardcoded-English string in an
  otherwise fully-localized approval card): new approval_pending_count key in en
  + 14 locales, wired via t() with an English fallback.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-23 03:12:49 +00:00
nesquena-hermes 06f968da5d docs(changelog): #6040 gateway approvals by run ID (#6008) 2026-07-23 02:43:49 +00:00
nesquena-hermes 0d06ae3467 Merge remote-tracking branch 'origin/master' into stage/6040 2026-07-23 02:26:42 +00:00
nesquena-hermes 12ea6ec927 Merge pull request #6445 from nesquena/stage/6408
Release: Compact Worklog last-char finalize fix (#6408)
2026-07-22 19:09:22 -07:00
nesquena-hermes d64897c539 docs(changelog): #6408 anchor-prose last-char finalize fix 2026-07-23 02:04:19 +00:00
starship-s 5bf77d558a fix: finalize sealed anchor prose at completion (#6408)
Anchor-owned process-prose rows kept the newest character as streaming-markdown
lookahead until the turn settled, so Compact Worklog prose immediately before a
tool card appeared one character short mid-stream. Finalize the cached parser
once when the row becomes 'completed' (running the same media/link/table cleanup
as the main body parser), guarded by a 'finalized' flag + self-heal on edit.

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-07-23 01:46:01 +00:00
nesquena-hermes 5b325d1472 Merge pull request #6443 from nesquena/stage/6319
Release: public-share local-media leak fix (#6319, P0 #6174)
2026-07-22 18:28:20 -07:00
nesquena-hermes ddb6210c06 docs(changelog): #6319 public-share media leak fix (#6174) 2026-07-23 01:23:37 +00:00
nesquena-hermesandwebtecnica 681adf0597 test(share): adversarial regression for public-share media embed boundary (#6174)
Covers traversal, symlink-escape, absolute-outside, file://, extension-spoof,
SVG, oversize, empty-allowed-roots (fail-closed), public-URL pass-through, and
the _sanitize_message integration — proven red-before-green against master.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-23 01:22:56 +00:00
webtecnica 02b5f885c8 fix(share): harden _embed_share_media with path allow-list + mime validation (#6174)
Embed local MEDIA: image refs as inline base64 at snapshot-build time,
gated by a workspace/attachment-root allow-list, 512KiB cap, image-MIME
allow-list, and magic-byte content validation. Everything else (traversal,
symlink-escape, file://, non-image, SVG, oversize) becomes a static
placeholder. Closes P0 #6174 (public shares no longer emit authenticated
/api/media requests for local paths).

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-23 01:21:59 +00:00
nesquena-hermes f26abae544 Merge pull request #6439 from nesquena/stage/6113
Release: #6113 per-turn used-model chip in transparent footer
2026-07-22 15:18:52 -07:00
nesquena-hermes e5096d06a0 Release: #6113 per-turn used-model chip in transparent footer 2026-07-22 22:13:25 +00:00
nesquena-hermes bbb417b3f1 stage: merge #6113 (per-turn used-model chip in transparent footer) 2026-07-22 22:13:08 +00:00
nesquena-hermes 9e1b8a115a Release: #6390 preserve scroll position after live-to-final settlement (#6436)
Release: #6390 preserve scroll position after live-to-final settlement
2026-07-22 13:59:04 -07:00
nesquena-hermes ef003ec958 Release: #6390 preserve scroll position after live-to-final settlement 2026-07-22 20:43:28 +00:00
nesquena-hermes 6e2267dd5f stage: merge #6390 (scroll preservation after live-to-final settlement) 2026-07-22 20:43:04 +00:00
Rod Boev 8119d91dc3 fix(#6008): serialize identity-less approval relays 2026-07-22 15:06:33 -04:00
nesquena-hermes 699e592e85 Release exp-batch1: #6372 #5797 #5944 (#6425)
Release exp-batch1: MIME safety (#6372), atomic config writes (#5797), ctl.sh foreign-instance guard (#5944)
2026-07-22 12:05:00 -07:00