Commit graph

333 commits

Author SHA1 Message Date
nesquena-hermes
0dba26d06e
Release v0.51.562 — selected-context quote cards (#4380) (#4629)
* stage #4380 (santastabber): render selected context as quote cards + CHANGELOG

Evolves the shipped #2543 named-context-blocks: composer chips -> quote cards
(accent rail, editable label w/ rename, remove, clipped excerpt) + thread render
of labeled selections as <figure> cards. Label+quote esc()'d on every path
(adversarial harness 0 survivors). 3 new i18n keys in all 13 locales (parity green).
Code applied from PR head 2eefc11c02a7 (own 3-dot contribution, CHANGELOG re-stamped).

Co-authored-by: santastabber <santastabber@users.noreply.github.com>

* fix #4380 Codex gate finding: selection-only reply must enable the primary Send button

Codex CORE: #4380 moved selected context out of the textarea into _pendingSelections,
but _composerHasContent() only checked textarea+files -> getComposerPrimaryAction()
returned disabled for a selection-only reply (desktop Enter calls send() directly so
it worked, but click/tap/mobile users were blocked). Fix: expose
window._hasPendingSelections() from messages.js, fold it into _composerHasContent(),
and call updateSendBtn() from _renderSelectionChips() so add/remove/clear refreshes
the button. + regression test.

* Release v0.51.562 — Release TU (selected-context quote cards; #4380)

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 12:24:42 -07:00
nesquena-hermes
236b165b7a harden(#4587): align scene-builder final-answer filters to the renderer's >=0.9 ratio
Codex gate (SILENT): the scene-builder/hydrator _anchor_scene_row_looks_like_final_answer
(messages.js + routes.py) used a loose '>=80 + prefix' rule while the renderer
_anchorSceneProseMatchesFinalAnswer uses '>=80 + ratio>=0.9'. A short intermediate-prose row that
was a prefix of the final answer could be dropped from the persisted scene by the looser builder,
leaving the stricter renderer no row to show -> silent prose loss. Aligned both filters to the
renderer's rule (drop fewer -> preserve more intermediate prose). Matrix already GREEN; this closes
the edge the fixture didn't exercise.
2026-06-21 06:03:44 +00:00
nesquena-hermes
6951d10ae5 rebase #4411 onto master (v0.51.542) for deep-review — Stable Assistant Turn Anchors capstone
franksong2702's activity_scene_v1 anchor-scene model: live-stream/settled/refresh/re-entry
show the same ordered turn. Rebased clean (no conflicts) onto current master.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 21:48:55 +00:00
jja881
53affd6936 fix: address review feedback — remove redundant clause, add guard and tests
- Remove the redundant 4th OR clause from _catalog_has_provider() — the
  existing C1–C3 already handle compound provider IDs correctly (C2 IS
  a normalized-vs-raw comparison; the 4th clause was a strict subset).
- Add && _activeProvider guard to _isCrossProviderPick in messages.js
  to prevent spurious True when the profile has no configured default
  provider (reviewer's optional hardening suggestion).
- Add test_catalog_has_provider_compound_ids.py verifying that compound
  provider IDs like 'custom:glm-free-relay' are correctly recognized by
  the existing C1–C3 clauses.
2026-06-20 18:44:25 +00:00
jja881
96265f6ae4 fix: preserve cross-provider model selection across conversation turns
Two-part fix for the model selector jumping back to the profile default
after the first message when a cross-provider model (e.g. a custom relay
like custom:glm-free-relay) is selected.

Backend (api/routes.py):
- _catalog_has_provider() now matches compound provider IDs (e.g.
  custom:zenmux-relay) whose normalized form (custom) is a raw
  catalog provider_id.  Previously the three existing checks missed
  normalized-vs-raw comparison for compound hints, causing the catalog
  lookup to fail and the fallback branch to silently revert the model
  to the profile default on every non-explicit turn.

Frontend (static/messages.js):
- The explicit_model_pick marker was consumed after the first send(),
  so subsequent sends lost the signal and the server repaired the
  model back to the profile default (#3737 regression).
- Now, when the session has a non-default model from a different
  provider than the profile's active provider, every send() sends
  explicit_model_pick=true so the server honors the user's choice
  across the entire conversation.
- The pending onchange marker is still consumed after the first send
  (unchanged behavior); only the explicit_pick inference is broadened.
2026-06-20 18:44:25 +00:00
nesquena-hermes
15fe6add30 stage #4524: clarify 409-terminal (id-guarded, loading-cleared) + SSE-notify on expiry (#4504) + v0.51.534 CHANGELOG 2026-06-20 05:36:56 +00:00
nesquena-hermes
ca19574b75 fix(notifications): make hidden-tracker stream-owned + clear on terminal paths (#4416 Codex gate)
Codex SHIP-WITH-FIXES: non-done terminal paths (apperror/cancel/stream-error
finalize) didn't clear _STREAM_WAS_HIDDEN, so a stale wasHidden=true could be
inherited by a later same-sid reconnect -> spurious notification. Make each
entry stream-owned ({streamId, wasHidden}) so a stale entry from a prior stream
is never trusted (reconnect keeps state only when streamId matches; done reads
via the owned entry), and add _clearStreamHidden(activeSid, streamId) on the
apperror/cancel/finalize-fallback terminal paths (belt-and-suspenders).
2026-06-19 23:54:23 +00:00
nesquena-hermes
d473f6c5a6 fix(notifications): notify on completion if tab was hidden during the stream (#4416)
A background tab's SSE is throttled by Chromium, so the stream 'done' event
arrives late (after the user returns, document.hidden=false) and the
response-complete notification was suppressed by the live visibility check.
Track per-stream whether the tab was hidden at ANY point during the stream
(_STREAM_WAS_HIDDEN, set at attach, OR'd by one idempotent visibilitychange
listener, read+cleared at done) and pass forceHidden accordingly.
sendBrowserNotification bypasses ONLY the live visibility gate on forceHidden,
still honoring the user's notifications-enabled setting — so a watched stream
stays silent (matches Slack/Discord/Gmail/Claude) but a backgrounded one
notifies. Correct fix for the #4416 root cause; supersedes #4421's always-fire
approach (which would also fire when the tab was never hidden).

Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
2026-06-19 23:48:42 +00:00
Frank Song
36e992415b Fix post-start attach resilience for #4481 2026-06-19 17:51:36 +08:00
nesquena-hermes
46f234f4fa release: v0.51.509 — notice when connected gateway can't do approvals (#4456, #4300)
Includes a showToast arg-order fix (4000ms,'warning') caught at gate.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

Closes #4456
2026-06-19 03:14:08 +00:00
nesquena-hermes
f94508994f release: v0.51.505 — allow macOS Mail message: links in chat markdown (#4420, #4319)
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>

Closes #4420
Closes #4319
2026-06-19 01:58:28 +00:00
bergeouss
d812f70116 fix(#4313): guard EventSource.close() calls to suppress ERR_ABORTED noise
Add readyState !== CLOSED (2) guards to all EventSource.close() calls across
messages.js, sessions.js, panels.js, and terminal.js. Chromium reports
net::ERR_ABORTED when close() is called on an already-closed EventSource,
polluting the console during normal lifecycle transitions (session switch,
panel close, stream teardown).

Key hotspots:
- closeLiveStream() double-close via onerror → _closeSource chain
- _closeSessionEventsSSE, _gatewaySSE.onerror missing try/catch
- _kanbanStopPolling, terminal close handlers

20 close() calls guarded across 4 files, no structural changes to stream
lifecycle.
2026-06-18 17:25:18 +00:00
nesquena-hermes
8246b4aad4 feat(#2543): collect selected chat text as named context blocks
"Reply with selection" no longer appends the selected text straight into
the composer. It now adds a named chip ("Context N") to a row above the
message box (composerSelectionChips). Chips can be renamed (double-click,
esc()-escaped) or removed (x); multiple stack. On send(),
_flushSelectionBlocksToComposer() materializes pending blocks into the
composer as **Name:** + markdown-quoted text, then the normal send path
runs unchanged on composer.value.

Regression-safety:
- _flushSelectionBlocksToComposer early-returns when no blocks are pending,
  so send() is byte-identical for anyone who never uses the feature.
- loadSession() AND newSession() both clear pending blocks before replacing
  S.session, so chips never leak across a sidebar switch OR a New Chat
  (the New Chat clear + its regression test were added per Codex review).
- chip name/text rendered via esc(); XSS-safe.

Verified live (headless CDP drive): add/render/remove/rename, flush
produces correct markdown, empty-flush no-op, session-switch clears,
injected <img onerror=...> chip-name escaped (not executed).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 20:15:02 +00:00
nesquena-hermes
4817e3a6ab
Release PB: honest notification-permission controls (#4118) (#4265)
ai... rodboev's #4133, notification-feature ONLY (dropped the unrelated
'invalidate sidebar cache on WAL' commit — that's a separate good fix to split
out). Button now reflects live Notification.permission instead of silent no-op.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 13:42:46 -07:00
nesquena-hermes
5812df5f7b Merge #4112 (skill bundles in WebUI slash commands, #4087) onto master 2026-06-13 23:13:28 +00:00
nesquena-hermes
f7047d4d19 Merge #4072 (wire /credits through WebUI command dispatch, #4071) onto master 2026-06-13 21:15:22 +00:00
nesquena-hermes
5a33a51cec Merge #4131 (badge pending prompts in document title, #1694) onto master
# Conflicts:
#	CHANGELOG.md
2026-06-13 19:19:24 +00:00
Rod Boev
e5640a42b9 Keep prompt-attention title badges from breaking title ownership 2026-06-13 15:05:03 -04:00
Rod Boev
4329768288 feat(topbar): badge pending prompts in document title (#4121) 2026-06-13 15:04:20 -04:00
Rod Boev
707a19e34c Keep busy-slash regressions aligned with /yolo support 2026-06-13 14:15:35 -04:00
Rod Boev
6c7d551b41 fix(commands): keep slash command ownership ahead of bundles (#4087) 2026-06-13 14:06:35 -04:00
Rod Boev
5a3c6c6f05 fix(commands): surface skill bundles in WebUI slash commands (#4087) 2026-06-13 14:06:35 -04:00
Rod Boev
61ddf4d21e fix(commands): run /yolo from busy slash intercept (#4117) 2026-06-13 13:59:04 -04:00
nesquena-hermes
310edac58e fix(#4108): add creation-time backstop cleanup to bound the registry-leak class
Codex found leak paths across 3 rounds (settled-restore, stream-end-fallback, reconnect-
preflight-inactive, stale-bailout, non-current-pane error, external cancelSessionStream).
Rather than chase every current+future teardown route, schedule an identity-guarded cleanup
at registry CREATION (600s) so window._liveAnchorRegistries self-expires no matter how the
stream ends — including external paths (boot.js cancelSessionStream) that bypass the SSE
handlers. Explicit terminal-path calls just expire it sooner. Shadow-only + identity-guarded,
so a rare >10min active stream only loses reconnect-continuity of an unconsumed registry.
2026-06-13 16:19:18 +00:00
nesquena-hermes
3cf96b1293 fix(#4108): cleanup anchor registry on reconnect-preflight-inactive + stale-stream-bailout paths (Codex round 2)
Two more early-return paths created the registry (early in attachLiveStream) then
returned without scheduling cleanup: (1) reconnect preflight when /api/chat/stream/status
is inactive/no-replay (~3966); (2) _bailOutOfTerminalEventsFromStaleStream (~1490) when a
stale stream's terminal event arrives after a newer stream owns the session. Both now
schedule identity-guarded cleanup (map.get(streamId)===registry, keyed on the stale
closure's own streamId so the newer registry is untouched).
2026-06-13 16:13:55 +00:00
nesquena-hermes
9594b97223 fix(#4108): schedule anchor registry cleanup on settled-restore + stream-end-fallback terminal paths (Codex leak catch)
Codex found window._liveAnchorRegistries retained registries for streams ending via
_restoreSettledSession() and _finalizeStreamEndFallback() (both terminal-close without
cleanup). Added _flushReasoningToAnchor()+_scheduleAnchorRegistryCleanup() to both —
identity-guarded delete already prevents clobbering a newer registry. All anchor helpers
are nested in attachLiveStream's closure (hoisted), ESLint runtime gate clean.
2026-06-13 16:09:20 +00:00
Rod Boev
59562e416a fix(#4071): wire /credits through WebUI command dispatch 2026-06-13 07:50:16 -04:00
Frank Song
aa96bc52bc Add assistant turn anchor live shadow feed 2026-06-13 16:26:52 +08:00
nesquena-hermes
e4bdcbfeae Merge #3823 (tool iteration limit terminal state) onto master
# Conflicts:
#	CHANGELOG.md
2026-06-13 05:06:46 +00:00
nesquena-hermes
c02c31c837 Merge #4006 (scroll auto-follow setting + pin guards) onto master
# Conflicts:
#	api/config.py
#	static/panels.js
2026-06-13 03:54:11 +00:00
nesquena-hermes
92f7a24448 Merge #4024 Transparent Stream onto master (rebase)
# Conflicts:
#	CHANGELOG.md
2026-06-13 00:40:17 +00:00
nesquena-hermes
1e5a741f50 Merge #4054 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes
10481d4f34 Merge #4051 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes
99ee11a733 Merge #4046 into stage-mj 2026-06-12 20:14:42 +00:00
Rod Boev
65c0864e34 fix(approval): clear stale approval cards while busy 2026-06-12 11:55:43 -04:00
Rod Boev
80dab6f3eb fix(commands): inject forced skill content for /use (#4044) 2026-06-12 08:34:27 -04:00
Rod Boev
1529dd0841 fix(commands): route /reload-skills through the WebUI allowlists (#4043) 2026-06-12 08:31:11 -04:00
Rod Boev
7795f29946 fix(composer): preserve send-button sync under scheduled resize (#4042) 2026-06-12 08:18:04 -04:00
Rod Boev
aab43cdaff fix(approval): keep approval polling alive across busy dips (#4041) 2026-06-12 08:12:57 -04:00
Rod Boev
768ea5ee87 fix(composer): coalesce composer auto-resize reflows (#4042) 2026-06-12 07:54:48 -04:00
Mohamed Habib
306a148fbf merge: sync transparent stream branch with master 2026-06-12 05:09:01 +03:00
Frank Song
ec23dac922 Handle tool limit terminal state 2026-06-12 10:03:31 +08:00
Mohamed Habib
fcfaa4686a feat: add transparent stream activity display 2026-06-12 04:45:41 +03:00
Ming
e05b99b7fd fix(scroll): guard _scrollAfterMessageRender and _finishDone against pin hysteresis snap-back
Two scroll-state bugs cause the viewport to snap to the bottom after
streaming completes, even when the user has scrolled up to read:

1. _scrollAfterMessageRender (ui.js): when preserveScroll=true and
   _scrollPinned=true (re-set by the 250px near-bottom hysteresis),
   _followMessagesAfterDomReplace() calls scrollToBottom(), overriding
   the user's position. Added !_messageUserUnpinned guard so explicitly
   scrolled-up users get their snapshot restored instead.

2. _finishDone (messages.js): the explicit scrollToBottom() at L3302
   fires when shouldFollowOnDone is true, but that flag only checks a
   120px near-bottom threshold. Added _isMessagePaneNearBottom(250)
   gate so users reading 250px+ above bottom keep their position.

Also adds a new 'auto_scroll_follow' setting (default: off) that gates
scrollIfPinned() and _shouldFollowMessagesOnDomReplace(). When disabled,
the viewport never auto-scrolls during streaming — the user controls
scroll position manually and uses the ↓ button to jump to bottom.

7-file setting-add pattern applied:
- api/config.py: _SETTINGS_DEFAULTS + _SETTINGS_BOOL_KEYS
- static/boot.js: both init paths
- static/ui.js: scrollIfPinned() + _shouldFollowMessagesOnDomReplace()
- static/index.html: checkbox in appearance settings
- static/i18n.js: en + zh-CN translations
- static/panels.js: payload, autosave, populate, full save
- messages.js: no additional changes (gated via _shouldFollowMessagesOnDomReplace)

Tests updated: test_tars_scroll_reset_regressions, test_issue1690,
test_issue3545. All 116 scroll-related tests pass.
2026-06-11 21:48:14 +02:00
nesquena-hermes
2cbae63a8d fix(#3996): preserve pending sid on hidden-tab skip so a bg-tab session reopens (Codex CORE)
If startSessionStream(sid) runs while the tab is ALREADY hidden (session loaded/
restored in a background tab), it set _sessionStreamSessionId then returned at the
hidden-skip WITHOUT recording _sessionStreamHiddenSid — so the visibility handler
(which reopens only from the holder) never reattached, silently dropping
bg_task_complete/server_turn_started until the next explicit startSessionStream.
Set _sessionStreamHiddenSid = sid on the hidden-skip return. Adds a regression test.
2026-06-11 19:17:11 +00:00
nesquena-hermes
5fdfb70bce fix(#3996): reopen per-session SSE on tab re-show (reopen-bug fix)
#3996 closes the gateway + per-session SSE on hidden tabs to free HTTP/1.1
connection-pool slots (#3992). But the per-session-stream visibility hook
guarded its reopen on _sessionStreamSessionId, which stopSessionStream() nulls —
so the per-session SSE closed on hide but never reopened on re-show (regressing
live-stream reattach). Capture the id into a dedicated _sessionStreamHiddenSid
before the close and reopen from it on visibility return. Gateway hook (sessions.js)
was already correct (reopens unconditionally + re-guards internally).
2026-06-11 19:10:38 +00:00
Dima Diall
832ab09324 fix(visibility): close gateway/session SSE on hidden tabs to prevent connection pool exhaustion (#3992) 2026-06-11 18:10:29 +01:00
nesquena-hermes
6bac70d298 fix: preserve live stream output across session switches (cross-client)
Adds a server-side run-journal live snapshot (_run_journal_live_snapshot) returned
in GET /api/session as runtime_journal_snapshot, so a FRESH client (another device,
or a tab with no in-memory snapshot) opening an in-progress session immediately sees
the already-streamed assistant text + tool cards rebuilt from the server. Composes
with the existing _replay_run_journal cursor path (seeds lastRunJournalSeq so replay
resumes from the snapshot cutoff, not duplicating it) and keys tool cards by the same
5 id aliases (tid/id/tool_call_id/tool_use_id/call_id) as #3763 so SSE replay replaces
rather than duplicates snapshot cards. Payload values truncated; redaction test added.

Co-authored-by: t3chn0pr13st <technopriest@live.ru>
2026-06-10 20:12:05 +00:00
nesquena-hermes
19080f73b1
Release v0.51.348 — Release LL (Phase 0 hotfix: timeout regression + data-loss + leaks) (#3917)
* stage v0.51.348: Phase 0 hotfix — approval/clarify timeout regression (#3913), queue/draft durability (#3906), settings auto-reopen (#3909), kanban FD leak (#3904)

* stage v0.51.348: re-anchor 4 SSE frontend tests to poll-only design (#3913); apply Opus SHOULD-FIX — immediate first poll tick so pending approval/clarify cards show instantly

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-10 01:12:48 -07:00
nesquena-hermes
e8d71a2fb8
Release v0.51.347 — Release LK (streaming & render reliability cluster #3892 #3898 #3885 #3882 #3868) (#3902)
* stage v0.51.347: render/stream cluster (#3892 #3898 #3885 #3882 #3868) + 2 Opus SHOULD-FIX

* stage v0.51.347: trim #3885 error-guard comment to fit diagnostic-test window

* Stamp v0.51.347 — Release LK (streaming & render reliability cluster)

* Remove stray uv.lock accidentally staged (not part of any cluster PR)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-09 17:02:35 -07:00