Commit Graph
7185 Commits
Author SHA1 Message Date
Rod Boev ca5f02bbff feat(#5759): add composer focus shortcut 2026-07-14 01:06:46 +00:00
nesquena-hermesandnesquena-hermes 11eb1b7a83 docs(changelog): stamp v0.52.28 stable section (first promotion since v0.52.0) (#6046)
Cuts the promoted range's bullets from [Unreleased] into a stamped
## [v0.52.28] section (41 bullets, v0.52.0..v0.52.28). Companion to the
v0.52.28 stable tag pushed by the promotion engine.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-13 16:54:47 -07:00
nesquena-hermes a1b60f304e Merge pull request #6044 from nesquena/stage/6042-ttft
Release exp: send-to-first-token latency instrumentation (#6042, #6006)
2026-07-13 15:56:49 -07:00
nesquena-hermes 7fb3d412f5 Release exp: instrument send-to-first-token latency (#6042, closes #6006). Thanks @rodboev.
Measure-only TTFT (ttft_ms) on the metering session + _firstTokenMs diagnostic. Computed once
on first token (no hot-path latency), all mutation under existing GlobalMeter._lock, existing
token/usage/cost metering unchanged. Codex SAFE (16-stream concurrency probe, no leak/None-deref)
+ stream-gate GREEN all modes + 22 tests.
2026-07-13 22:52:15 +00:00
nesquena-hermes f1480ad407 Merge branch 'rebase-6042' into stage/6042-ttft 2026-07-13 22:51:55 +00:00
Rod Boev 001d7985bc feat(#6006): instrument send-to-first-token latency 2026-07-13 17:56:44 -04:00
nesquena-hermes e5148f9dbe Merge pull request #6036 from nesquena/stage/6035-approval
Release exp: fix 'approve once' persisting as session approval (#6035, #6017)
2026-07-13 13:25:18 -07:00
nesquena-hermes ea5af7b6c8 Release exp: fix 'approve once' persisting as session approval (#6035, closes #6017). Thanks @rudidev08.
Local (non-gateway) _resolve_approval_legacy collapsed 'once'+'session' into approve_session()
→ 'approve once' silently persisted session-wide. Narrowed to choice=='session' only; 'once'
still resolves the current call but doesn't persist. Codex SAFE (adversarial: once resolves
current+no residue+next re-prompts, session/always/deny unregressed, gateway consistent, tests
non-vacuous + parked-thread probe of all 4 choices).
2026-07-13 20:16:26 +00:00
nesquena-hermes b86922ae38 Merge branch 'rebase-6035' into stage/6035-approval 2026-07-13 20:15:59 +00:00
rudidev08 34cf2de57a Fix: "Approve once" no longer persists as a session approval (#6017)
In the local (non-gateway) approval path, _resolve_approval_legacy grouped
"once" together with "session" and called approve_session() for both.
approve_session() records the pattern in the session-wide allowlist that the
guard later consults, so a single "Approve once" click silently approved every
later matching tool call for the whole session -- a destructive tool could then
run with no approval card.

Only "session"/"always" persist now. "once" falls through: the
resolve_gateway_approval() call below still unblocks the parked agent thread for
every choice, so the current tool call proceeds, but nothing is written to
_session_approved and the next matching guarded call re-prompts.

Adds tests/test_issue6017_approve_once_no_persist.py, asserting is_approved is
False after "once" and True after "session".
2026-07-13 13:05:52 -07:00
nesquena-hermes 07a1fad2c8 Merge pull request #6034 from nesquena/stage/6019-a11y
Release exp: a11y keyboard access for conversation actions menu (#6019)
2026-07-13 12:42:32 -07:00
nesquena-hermes d6684c091f Release exp: keyboard/screen-reader access for conversation actions menu (#6019). Thanks @sheldon-im.
ARIA (aria-expanded/controls, role=menu/menuitem) + focus-to-first-action on open +
Arrow/Home/End/Escape nav + focus-restore-to-trigger on close; existing click/touch unchanged.
Codex SAFE (isConnected-guarded restore, menu-scoped bounded arrow-nav, no listener leak
[25-cycle probe], 49 tests) + stream-gate GREEN.
2026-07-13 19:37:56 +00:00
nesquena-hermes e02636c58c Merge branch 'rebase-6019' into stage/6019-a11y 2026-07-13 19:24:30 +00:00
sheldon-im d7c2cc2d5d Restore focus from non-button session action menus 2026-07-13 19:24:09 +00:00
sheldon-im bc85711af2 Improve conversation actions menu keyboard access 2026-07-13 19:24:09 +00:00
nesquena-hermes 41f1eb5624 Merge pull request #6032 from nesquena/stage/r2-batch
Release exp: Round-2 batch — picker dedup (#5997) + zh-CN i18n (#6024)
2026-07-13 12:20:51 -07:00
nesquena-hermes 735d74df46 Release exp: Round-2 batch — model-picker dedup (#5997) + zh-CN localization fill (#6024)
#5997 (rodboev, closes #5989): proxy/catalog model-picker dedup, 4-round convergence.
  Colon-suffix collision closed (provider-context exact-prefix strip, :free/:thinking/:latest
  survive), 5 prior findings stay fixed. Codex SAFE r4 (73 tests) + stream-gate GREEN.
#6024 (Loukky): zh-CN localization fill (+55/-55, pure value replacement on existing keys,
  no key add/remove, no value-swap [en block 0 CJK], placeholders preserved).
Disjoint (static/ui.js picker logic / static/i18n.js locale data).
2026-07-13 19:17:13 +00:00
nesquena-hermes 4016130c24 Merge branch 'rebase-6024' into stage/r2-batch 2026-07-13 19:04:13 +00:00
nesquena-hermes 54a7bb2058 Merge branch 'rebase-5997' into stage/r2-batch 2026-07-13 19:04:12 +00:00
Loukky d1aa5945b6 Update i18n.js 2026-07-13 18:57:49 +00:00
Loukky 6ccf9168f2 [locale]Localization for Simplified Chinese
Translate all untranslated English strings into Simplified Chinese.
2026-07-13 18:57:49 +00:00
Rod Boev 3ada146d48 fix(#5989): keep colon-suffixed proxy models distinct 2026-07-13 18:57:15 +00:00
Rod Boev ff89f2ebd6 fix(#5989): keep cross-provider live rows selectable 2026-07-13 18:57:15 +00:00
Rod Boev 58725d5de9 fix(#5989): separate picker dedup from restore matching 2026-07-13 18:57:15 +00:00
Rod Boev 052effcfc4 fix(#5989): deduplicate proxy and catalog model rows in the picker 2026-07-13 18:57:15 +00:00
nesquena-hermes e4188ccd5e Merge pull request #6031 from nesquena/stage/6028-solo
Release exp: worktree unlock-before-remove (#6028, #6023)
2026-07-13 11:31:19 -07:00
nesquena-hermes 996705b7b1 Release exp: worktree unlock-before-remove so WebUI worktrees are reapable (#6028, #6023). Thanks @ayxuerui.
Fail-soft git worktree unlock before remove (agent locks worktrees it creates; git refuses
--force remove on locked → orphan accumulation). Dirty-guard still runs first. Codex SAFE,
13 tests pass. Shipped solo (unbundled from #6013 which has a full-suite test-interaction).
2026-07-13 18:27:44 +00:00
nesquena-hermes 1e2a01102a Merge branch 'verify-6028' into stage/6028-solo 2026-07-13 18:27:23 +00:00
Rui Xue c833f290a9 fix(worktrees): unlock before remove so WebUI-created worktrees can be reaped
WebUI-created worktrees are locked at creation by the agent
(cli._setup_worktree locks with --reason 'hermes pid=...'), but
remove_worktree_for_session never unlocked, so git refused every removal
with "use 'remove -f -f' to override or unlock first" — force=true
included, since a single --force cannot override a lock.

Mirror the agent's own _cleanup_worktree ordering: fail-soft
'git worktree unlock' right before the remove, after all
dirty/untracked/unpushed/stream/terminal guards have passed. Those
guards remain the real safety layer; force keeps meaning 'override
dirty working state', never 'override a foreign lock'.

Tests: locked worktree now removes cleanly without force; unlock is
fail-soft when the worktree was never locked; existing mocked
call-order assertions updated for the leading unlock call.

Fixes #6023
2026-07-13 17:47:58 +00:00
nesquena-hermes d486394f7b Merge pull request #6012 from nesquena/stage/5802-media
Release exp: render MEDIA: tokens inline during streaming (#5802)
2026-07-13 01:21:25 -07:00
nesquena-hermes 42d62e0793 Release exp: render MEDIA: tokens inline during streaming (#5802). Thanks @silent-reader-cn.
MEDIA tokens (image/video/audio/pdf/diff/csv/excalidraw) render inline in real time
during streaming incl split-across-chunk markers. Codex SAFE r4 (both prose regressions
closed — ownership preserved + fade retained, normal prose byte-identical to master, all
4 original findings fixed, security clean, no ReDoS), stream-gate GREEN all modes (hide_all
RED was load flake, isolated re-run clean). 227 tests + 18 real-SMD subtests pass. 4-round
convergence on the crown-jewel render + security path.
2026-07-13 08:17:52 +00:00
nesquena-hermes 2d1a2d15a0 Merge branch 'rebase-5802' into stage/5802-media 2026-07-13 08:06:28 +00:00
silent-reader-cn 946f7dbc2b fix(media): preserve scanner prose ownership 2026-07-13 07:41:13 +00:00
silent-reader-cn e13a75b7b1 test(media): cover fade MEDIA streaming splits 2026-07-13 07:41:13 +00:00
silent-reader-cn 3b19660235 fix(media): handle fade MEDIA prefix splits 2026-07-13 07:41:13 +00:00
silent-reader-cn a17370ad4a fix(media): harden streamed MEDIA rendering 2026-07-13 07:41:13 +00:00
silent-reader-cn 188a03bb28 fix: remove unused smd media test variable 2026-07-13 07:41:13 +00:00
silent-reader-cn 76940e821d fix: flush smd media tail on stream end 2026-07-13 07:41:13 +00:00
silent-reader-cn 547c218ddf fix: clear fallback smd media tail 2026-07-13 07:41:13 +00:00
silent-reader-cn ca3a1ac04d fix: accept extensionless streaming media urls 2026-07-13 07:41:13 +00:00
silent-reader-cn 13b7080bf0 fix: align smd media boundary extensions 2026-07-13 07:41:13 +00:00
silent-reader-cn 5359b70c6a fix: harden smd media split-tail streaming 2026-07-13 07:41:12 +00:00
silent-reader-cn 7cf8823921 fix: remove unused smd media test variable 2026-07-13 07:41:12 +00:00
silent-reader-cn d2a696d09a fix(webui): resolve Greptile #3 — WeakMap string key crash + unused _smdMediaTailClear
Greptile re-review (3/5) flagged two issues:

1. parserFor fallback ('__default__') string key → WeakMap.set throws
   TypeError (WeakMap accepts only object keys). Fixed by introducing a
   module-level sentinel object __SMD_PARSER_FALLBACK = {} and using it
   in both parserFor definitions instead of the string literal.

2. _smdMediaTailClear was defined but never called despite the comment
   claiming it was. Fixed by adding calls in:
   - _smdEndParser → clears the ongoing live-stream parser's tail
   - _clearAnchorProseIncrementalNode → clears each anchor-scene
     parser's tail before the cache is dropped

All 222 tests remain green.
2026-07-13 07:41:12 +00:00
silent-reader-cn 0b14ab54e3 fix(webui): split prose/MEDIA paths in streaming interceptor + per-parser tail buffer
Code-level fixes for the two issues Greptile + owner review surfaced:

1. SAFETY (Greptile #1 / owner security regression): the original
   _smdMediaAwareAddText concatenated prose slices + MEDIA HTML into
   one string and handed it to DOMParser. Prose containing HTML
   metacharacters (e.g. <img onerror=...>) would be parsed as live
   elements. Fixed by routing each prose run through baseAddText
   (which calls createTextNode) and only feeding each single MEDIA
   token's trusted _inlineMediaHtmlForRef output through DOMParser.

2. CROSS-CHUNK SPLITS (Greptile #2): smd may deliver a MEDIA: token
   across two flushes (e.g. "MEDIA:C:\...\" then "foo.png"). The
   original implementation would treat each half as separate text
   because neither half terminated cleanly. Fixed with a per-parser
   tail buffer (WeakMap keyed by parser instance) that holds trailing
   bytes ending in "MEDIA:" or MEDIA:<partial-ref> until the next call.

   The tail-buffer boundary detection was also improved: the previous
   `/[\s\)\]]/.test(rest)` incorrectly rejected "before MEDIA:" (the
   whitespace is before MEDIA, not after a completed ref). Replaced
   with `rest.endsWith('MEDIA:')` and `/MEDIA:...$/.exec(rest)`.

Architecture: _smdAppendMediaNode is a new single-token DOM insertion
helper that creates a DocumentFragment from one _inlineMediaHtmlForRef
output. _smdMediaTailSet/_smdMediaTailClear manage the per-parser
buffer. Both _safeSmdRenderer and _streamFadeRenderer now route through
the updated interceptor with tail map and parser identity.

Coverage: test_smd_media_in_stream.py tests the architectural invariants
(helper exists, both renderers delegate, recursion guard, entity-safety,
cross-chunk tail). 222 tests pass across all renderer/behaviour suites.
2026-07-13 07:41:12 +00:00
silent-reader-cn 8dda917442 chore: gitignore playwright-mcp scratch and final-check.png
The local agent loop drops:
- a `.playwright-mcp/` directory full of intermediate screenshots while
  debugging browser sessions
- a `final-check.png` of the page state at the end of a task

Both are local-only debugging artifacts that should never end up in a
commit. Add them next to the existing `screenshot-*.png` / `full-UI.png`
rules so the same dev-loop garbage collection pattern continues to hold
without anyone having to think about it.
2026-07-13 07:41:12 +00:00
silent-reader-cn 6950540ed5 fix(webui): render MEDIA: tokens inline during smd streaming
The full renderMd() pipeline maps MEDIA:<path-or-url> tokens to inline
images / video / audio / SVG / PDF / etc. via stash + restore passes.
The streaming smd renderer used by the live assistant body never went
through that pass, so any MEDIA token emitted mid-turn showed up as the
raw path string in the chat window and only became a real <img> once
the turn settled and the full render fired.

Fix:
- Extract the URL/media → HTML mapping in renderMd() into a shared
  helper _inlineMediaHtmlForRef(ref, sessionId) so the streaming path
  can reuse the same DOM the settled message will eventually render.
- Add _smdMediaAwareAddText in messages.js that wraps smd's add_text
  to detect MEDIA tokens in any incoming text chunk and splice the
  resulting <img>/<video>/<a>/<div data-path=...> node directly into
  the smd parent via a DOMParser-created fragment. Plain prose still
  delegates to the renderer it intercepted (so the fade renderer keeps
  its word-by-word animation, the safe renderer keeps its O(n) plain
  text-node insertion).
- Route _safeSmdRenderer.add_text and _streamFadeRenderer.add_text
  through the interceptor. Each fast-paths the no-MEDIA case to the
  original baseAddText so we can't recurse into our own wrapper.

Coverage:
- New test_smd_media_in_stream.py locks in the architectural invariants
  (helper exists, both renderers delegate to it, recursion guard).
- test_renderer_js_behaviour.py driver now provides a minimal
  _inlineMediaHtmlForRef stand-in so renderMd can be invoked standalone
  under node without the rest of ui.js in scope (matches the patterns
  renderMd already relied on for _IMAGE_EXTS / _SVG_EXTS / _AUDIO_EXTS).
- test_svg_audio_video_rendering.py static-count assertions relax from
  ">=2" to ">=1" since audio/video/SVG markup now lives in a single
  shared function rather than being copy-pasted inside renderMd's
  stash-restore closure.
2026-07-13 07:41:12 +00:00
nesquena-hermes 2b3016bf26 Merge pull request #6004 from nesquena/stage/5969-solo
Release exp: gateway terminal errors + persist pending turn (#5969, closes #5940)
2026-07-12 20:50:17 -07:00
nesquena-hermes ab1d909aaa Release exp: gateway terminal errors + persist pending turn (#5969, closes #5940). Thanks @rodboev.
Gateway terminal provider errors classified+surfaced (not generic empty turn), session_id-bound,
pending turn persisted through reload incl repeated-prompt (is_exact_checkpoint). Codex SAFE r4 +
stream-gate GREEN all modes. Shipped solo (unbundled from #6002 which has a test-isolation CI fail).
2026-07-13 03:42:36 +00:00
nesquena-hermes f777175cf9 Merge branch 'rebase-5969b' into stage/5969-solo 2026-07-13 03:41:39 +00:00