Commit graph

6786 commits

Author SHA1 Message Date
Frank Song
eba071203d Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-08 09:07:55 +08:00
Frank Song
68e4770c2b fix(wakeup): revalidate recovered credential pauses 2026-07-08 08:56:05 +08:00
nesquena-hermes
07b9708cde
Merge pull request #5756 from nesquena/release/stage-5751
Release: non-virtualized transcript scroll jump-back (#5751)
2026-07-07 14:27:32 -07:00
nesquena-hermes
e4eb330da5 docs(changelog): #5751 non-virtualized transcript scroll jump-back 2026-07-07 21:17:41 +00:00
allenliang2022
e8a3412939 fix(webui): reserve real user-row height in non-virtualized transcripts to stop scroll jump-back
When transcript virtualization is disabled (the #4325 opt-out,
_virtualizeTranscript===false), renderMessages() renders every row with no
windowing and never runs the virtualized measure pass
(_updateMessageVirtualMeasurements early-returns when !virtualized). Under
@media (pointer: coarse), .msg-row[data-role="user"] carries
content-visibility: auto; contain-intrinsic-size: auto 96px. Every rebuild does
inner.innerHTML='' then recreates rows as fresh elements, so a fresh off-screen
tall user row (a long paste measuring thousands of px) reserves only the flat
estimate instead of its real height. scrollHeight shrinks by (realHeight -
estimate), the browser force-clamps scrollTop, and the viewport jumps backward
(a browser clamp, JS=none, so scrollTop-write compensation cannot catch it).
#5638 fixed this for the virtualized wipe-and-rebuild path but left the
non-virtualized full-rebuild path uncovered.

Fix, three coordinated parts:
- _estimateUserRowIntrinsicHeight weights CJK / full-width characters as ~2
  columns (they wrap at ~24 chars/line, not 48), so a CJK paste reserves close
  to its real height even before it is ever measured.
- _applyUserRowIntrinsicHeight reserves max(remembered, estimate): a remembered
  height can be a partial paint (a row taller than the viewport only paints its
  intersecting slice under content-visibility:auto), so the estimate floors it.
- _rememberRenderedUserRowIntrinsicHeights, called pre-wipe inside
  renderMessages, reads the still-laid-out rows' real heights and persists them
  keyed by session-relative index, only for rows within the viewport (a fully
  off-screen never-painted row reports its collapsed reserve and must not poison
  the map), floored at the estimate.

Desktop rests at content-visibility:visible so intrinsic-size is inert there;
verified no behavior change with pointer:fine.

Adds tests/test_issue5744_nonvirtual_userrow_collapse_jumpback.py (7
mutation-checked node-harness tests). Existing #5637/#5638 suites and the
render/virtualization suites pass.
2026-07-07 21:17:03 +00:00
Frank Song
ef85f5c5cb Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-08 04:27:16 +08:00
nesquena-hermes
923cadcb80
Merge pull request #5754 from nesquena/release/stage-5742
Release: desktop stale-snapshot scroll compensation (#5742)
2026-07-07 13:19:28 -07:00
nesquena-hermes
b40bc50d67 docs(changelog): #5742 desktop stale-snapshot scroll compensation 2026-07-07 20:13:23 +00:00
allenliang2022
fbfb6da8fe fix(webui): require an actual captured topPadBefore before the gone-anchor topPad-delta (greptile P1)
When the anchor row is gone and a virtual top-spacer is present but the snapshot
carries no captured topPadBefore (null), Number(null) is 0, so the topPad-delta
branch would add the ENTIRE current spacer height to scrollTop and fling the reader
far from their content. Guard on an actual (non-null) captured topPadBefore; else
keep the raw fallback target. Mutation-checked (drop the null guard -> the reader is
flung 1300px and the test fails).
2026-07-07 20:12:41 +00:00
allenliang2022
7164847255 fix(webui): realign desktop stale-snapshot fallback with the app's scrollTop += delta idiom (#5637 follow-up)
Round-2 gate-cert proved the round-1 'snapshot.top + delta' + nearest-to-current
arbiter is mathematically unwinnable: a row's rect offset is scroll-relative
(rect.top - containerRect.top = rowContentPos - scrollTop), so a staged absolute
target only equals the true content-hold when scrollTop == snapshot.top — and in
that exact case the arbiter measures |raw - cur| = 0 and picks raw, writing the
stale value (the reported up-jump, unfixed).

Rework to the app's own realign idiom (mirrors _restoreMessageViewportAnchor and
_compensateScrollForMeasurementDelta): when a measurable anchor row exists, write
el.scrollTop += (currentOffset - capturedOffset) from the LIVE scrollTop — a no-op
when already aligned, a heal when not, no arbiter. Restore the per-tier lookup
guard (key -> sessionIdx, never the rawIdx degradation that maps to a different
message after a virtualization re-window). Genuinely-gone anchor: mirror the
topPad-delta idiom the snapshot already carries (topPadBefore), else keep raw.

Gate-cert tests now use scroll-dependent rect mocks (rect.top = rowContentPos -
scrollTop) so certified hold numbers are physically realizable; mutation-checked
(revert to raw -> the hold tests fail; drop topPad-delta -> that test fails).
2026-07-07 20:12:41 +00:00
nesquena-hermes
57aa5be2ed
Merge pull request #5753 from nesquena/release/stage-5463-v2
Release: mirror WebUI usage counters to state.db (#5463)
2026-07-07 13:12:06 -07:00
nesquena-hermes
5915f9b5b5 docs(changelog): #5463 mirror WebUI usage counters to state.db 2026-07-07 20:05:18 +00:00
kosta
0560f2615b fix(state-sync): mirror WebUI API call counts 2026-07-07 19:48:19 +00:00
kosta
059a06b70d fix(state-sync): mirror WebUI cache counters 2026-07-07 19:48:19 +00:00
Frank Song
76478729b8 fix(wakeup): record pauses from turn route lane 2026-07-08 01:57:46 +08:00
nesquena-hermes
1b58e4df47
Merge pull request #5750 from nesquena/release/stage-5744
Release: clear dead steer busy state (#5723)
2026-07-07 09:45:35 -07:00
nesquena-hermes
cf769dc823 docs(changelog): #5723 clear dead steer busy state 2026-07-07 16:42:08 +00:00
rodboev
8b02a8c260 fix(#5723): complete stale-busy cleanup on dead-run recovery (Fable UX-gate)
_steerClearCurrentOwnerDeadRun cleared busy/activeStreamId/inflight but left
the status line, elapsed timer, and optimistic streaming badge lingering after
a recovered dead run. Add the rest of the app's stale-busy cleanup idiom
(setStatus/setComposerStatus/_clearActivityElapsedTimer/clearOptimisticSessionStreaming),
still bypassing setBusy(false) so its queue-drain can't clobber the restored draft.
2026-07-07 16:32:26 +00:00
Frank Song
101cd4bacc test(wakeup): cover custom pause lane shorthand 2026-07-07 17:03:17 +08:00
Frank Song
5764df66f3 fix(wakeup): share process pause lane resolution 2026-07-07 17:00:36 +08:00
Frank Song
1ab9c47e9b fix(wakeup): canonicalize paused route model lanes 2026-07-07 15:09:30 +08:00
Frank Song
09862a3f9a fix(wakeup): stabilize credential pause revalidation 2026-07-07 14:08:25 +08:00
Frank Song
de5cbd2561 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-07 13:22:17 +08:00
Frank Song
2c5035d6e7 fix(gateway): recheck cancel before wakeup pause clear 2026-07-07 13:21:36 +08:00
nesquena-hermes
c3ff9f41c7
Merge pull request #5741 from nesquena/release/stage-5738
Release: keep the composer above iPad keyboards (#5701)
2026-07-06 22:20:06 -07:00
nesquena-hermes
b65158c0ce docs(changelog): #5701 iPad keyboard composer inset 2026-07-07 05:16:34 +00:00
Frank Song
3acd588621 fix(gateway): preserve wakeup pause on late cancel 2026-07-07 13:15:54 +08:00
Frank Song
a61381ee7e fix(wakeup): key stale pause to failed lane 2026-07-07 13:08:27 +08:00
rodboev
56a395c5f3 fix(#5701): ignore pinch-zoom scale in keyboard-inset (Fable UX-gate)
vv.scale != 1 means the visual-viewport shrinkage reflects zoom, not the
keyboard; on Chromium 'force enable zoom' that produced a large spurious inset
that jittered on pan. Bail out of the inset when |vv.scale-1|>0.05. + test.
2026-07-07 05:07:19 +00:00
rodboev
ae8d0bf3fb fix(#5701): keep the composer above iPad keyboards
On touch-primary devices (iPad Safari) the on-screen keyboard shrinks the
visual viewport but not the layout viewport, so the bottom-pinned composer
sits behind the keyboard. Compute a --keyboard-bottom-inset from visualViewport
and pad the composer up by it; clear it when the keyboard closes / on
non-touch / no-visualViewport. Reuses the existing visualViewport + pageshow
hooks; no-op on desktop/fine-pointer.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 05:05:11 +00:00
Frank Song
46e788797e Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-07 13:01:42 +08:00
nesquena-hermes
021bc1280f
Merge pull request #5740 from nesquena/release/stage-5737
Release: drop empty tool_calls arrays before API send (#5737)
2026-07-06 21:56:50 -07:00
nesquena-hermes
1a2dcc7231 docs(changelog): #5737 drop empty tool_calls before API send 2026-07-07 04:52:55 +00:00
Swan-S
ccd62f9b9d fix(streaming): drop empty tool_calls arrays before API send (#5737)
Strict providers (DeepSeek v4, newer OpenAI) reject an assistant message
carrying tool_calls: [] with HTTP 400. _sanitize_messages_for_api already
strips orphaned tool results, but not an assistant message that literally
stores an empty tool_calls list.

Drop the empty key on ALL THREE sanitized-dict sites that must agree:
_sanitize_messages_for_api, _api_safe_message_positions, and the
_safe_projection inside _restore_reasoning_metadata (so a tool_calls: [] row
projects identically on both sides of the metadata-carry-forward alignment and
doesn't lose its reasoning/id/timestamp every turn — Fable gate follow-up,
folded in).

Added mutation-checked regression tests: empty tool_calls dropped + populated
chain preserved (both sanitizer paths), and metadata carry-forward survives a
tool_calls: [] row.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-07 04:43:46 +00:00
Frank Song
1424917c1d fix(streaming): keep checkpoint guard comment adjacent 2026-07-07 12:42:22 +08:00
Frank Song
8e2a56e563 fix(streaming): keep checkpoint guard adjacent to try 2026-07-07 12:40:07 +08:00
Frank Song
b25e99c1b4 fix(wakeup): revalidate credential pause on refresh 2026-07-07 12:34:41 +08:00
Frank Song
d72698b75d fix(wakeup): snapshot source for stale pause handling 2026-07-07 12:32:37 +08:00
Frank Song
477f567a19 fix(wakeup): preserve pause for stale credential streams 2026-07-07 12:29:32 +08:00
Frank Song
774e8897c4 fix(wakeup): preserve pause on stale stream error 2026-07-07 12:27:31 +08:00
Frank Song
0d346327e4 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-07 12:22:07 +08:00
nesquena-hermes
ad7d0abf44
Merge pull request #5736 from nesquena/release/stage-tlsflake
test(tls): drain full stdout for the TLS-fallback assertion (flake fix)
2026-07-06 21:14:27 -07:00
Frank Song
524353b62d fix(wakeup): keep pause on late cancel 2026-07-07 12:09:16 +08:00
nesquena-hermes
f36dad7b23 test(tls): drain full stdout for the TLS-fallback assertion (flake fix)
test_tls_startup_failure_fallback_to_http read only the first 2000 bytes of
server stdout to assert 'TLS setup failed'. When the test interpreter lacks
optional deps (requests/websockets), startup emits a burst of plugin/tool
import warnings + the startup-config banner BEFORE the TLS line, pushing the
marker past the 2000-byte window -> spurious failure. Drain all
currently-available stdout (5s deadline, short-circuit on the marker) instead
of a single fixed-size read. Verified failing->passing in a fresh venv without
the optional deps. Test-only; distinct from the agent-package-isolation flake
cause already fixed in the same test.
2026-07-07 04:09:15 +00:00
Frank Song
035daab795 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-07 12:02:30 +08:00
nesquena-hermes
b0c79fd4d3
Merge pull request #5735 from nesquena/release/stage-5567-resolver
Release: #5567 model-picker provider resolution fix
2026-07-06 20:56:50 -07:00
Frank Song
cfd1169126 fix(wakeup): preserve pause through cancelled turns 2026-07-07 11:55:48 +08:00
nesquena-hermes
4fb7b03200 docs(changelog): #5567 model-picker provider resolution fix 2026-07-07 03:52:04 +00:00
Frank Song
bcb411db53 fix(wakeup): pause credential-exhausted process wakeups 2026-07-07 11:45:01 +08:00
nesquena-hermes
8d496ceaef fix(#5567): resolve model provider by matching value, not the selected option
Frontend fix for the model-picker provider-contamination vector of #5567 (the
backend HERMES_HOME vector shipped in #5577). Credit @b3nw for the root-cause trace.

_modelStateForSelect(sel, modelId) resolved the provider from sel.selectedOptions[0]
(the dropdown's CURRENTLY-selected option) instead of the option whose value matches
modelId. During a profile/tab switch or a model-list rebuild the dropdown transiently
still holds the PREVIOUS profile's default option (e.g. an ollama model), so the wrong
provider was stamped onto a model it doesn't own, persisted into the session's
model_provider, and (since _modelProviderForSend reads the stored value first) re-sent
on every turn -> 'Provider ollama...no API key' for a provider the session never used.

Fix: resolve the provider from the option whose VALUE === modelId; trust the
currently-selected option only when its value matches (preserving the user's exact
pick in the same-value/different-provider collision case). A genuinely-absent model
yields a null provider so the backend re-infers.

This is the WRITE-site fix — it stops all NEW contamination (new-session + send paths
route through _modelStateForSelect). Both Codex and Fable reviewed it SAFE.

Repairing sessions ALREADY poisoned before this shipped is deliberately deferred to a
separate follow-up: a 3-round Codex+Fable gate proved a frontend load-time repair that
reads the shared #modelSelect DOM is a cross-session race surface (it re-manufactured
the reverse-contamination it aimed to fix), and it can't reliably heal live-only-model
sessions anyway. The robust place for that repair is the backend chat-start boundary,
where session + profile identity is unambiguous. Already-poisoned sessions self-heal on
their next healthy send / manual re-pick in the meantime.

Tests: node-behavioral resolver scenarios (tab-switch race, same-value collision,
missing-model null, match-by-value, default->null) + the unrelated-selected-option
case from #5727. Partially addresses #5567 (write-vector); follow-up tracks the
already-poisoned-session repair.

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-07-07 03:42:16 +00:00