Two CI-red issues, both fixed:
1. Lint (F401): the new tests/test_session_unread_dot_on_visit.py imported
`re` but never used it. Dropped the import.
2. Test hang/timeout: loadSession() now routes its viewed-count/unread clear
through the new _acknowledgeSessionVisit() helper, but the PRE-EXISTING
tests/test_cross_session_message_load_isolation.py harness (which extracts
loadSession and stubs its deps) didn't define it — so the extracted
loadSession broke and the node driver hung to the 60s pytest-timeout.
Stubbed _acknowledgeSessionVisit + _sessionVisitHasUnreadState to no-ops
there, mirroring the pre-existing _setSessionViewedCount /
_clearSessionCompletionUnread stubs they replaced. That harness covers
cross-session load ordering + stale-reject, not unread-dot state, so a
no-op stub preserves its intent; the real helper is exercised by the PR's
own test_session_unread_dot_on_visit.py.
Verified: ruff forward gate clean; the timeout test passes in ~1s; the PR's
unread-dot suite 7/7.
Salvaged from #4946 by @neaucode-bot. Rebuilt fresh on master (repro-confirmed),
without regressing hidden-tab completion state or lineage-parent unread dots.
Co-authored-by: neaucode-bot <neaucode-bot@users.noreply.github.com>
CI ruff gate flagged 3 new violations: two api/routes.py KeyError re-raises in an
except-KeyError block (add 'from None') + an unused importlib import in the share
security test. All trivial.
Czech (#5546) was added after #5337 authored its share i18n keys, so the
strict cs-parity test failed on the 14 missing share_session/stop_sharing keys.
Added them to the cs block (English fallback values per locale convention).
The re-gate found the always-on sanitizer still had two str()-on-dict paths:
a list text-block whose 'text' is itself a dict (via /api/session/import), and
a dict-valued session title. Both are now type-checked to str; a dict title
falls back to 'Untitled'. Regression cases added. Co-authored-by: MicoRobot.
Adversarial security re-gate on the rebased head found 4 issues; fixed:
1. [CORE] api/shares.py public snapshot could leak credentials/paths/tool
payloads. The sanitizer relied on redact_session_data (gated on the
user-toggleable api_redact_enabled) — disabling it exposed API keys, a dict
assistant content was stringified as raw tool data, and session.workspace
survived in transcript text. Now the per-message sanitizer applies ALWAYS-ON
credential redaction (_redact_fn_cached, force=True, ignores the setting) +
scrubs known session/workspace/home paths, and refuses to stringify non-text
structured content. Title is force-redacted too. Regression test proves the
boundary holds with api_redact_enabled=False.
2. [SILENT] api/models.py: my rebase wrongly kept anchor_activity_scenes in
METADATA_FIELDS, moving 250-480KB scene bodies before messages and undoing
master's metadata fast-path (#5854). Removed; 17 #5854 tests pass again.
3. [SILENT] static/index.html: the Share/StopSharing/ExportHTML button triplet
was double-applied by the rebase (duplicate IDs → only the first wired).
Deduped to one each + a count assertion test.
4. tests: fixed the stale Session.load-after-/api/session/new setup (master
keeps new sessions memory-only until first message).
Also (UX, found in the visual flow sweep): a clipboard-copy failure after a
SUCCESSFUL share showed a red 'Share failed' toast and skipped opening the page.
The copy is now isolated — the share succeeds, the page opens, and the toast
shows the link if the clipboard write fails, never a false failure.
Co-authored-by: MicoRobot.
Rebased 363 commits forward (branch-point 2026-07-05). Applied the PR's code
diff cleanly across 16 files; resolved api/models.py additively (both master's
process_wakeup_pause/anchor_scene_index fields AND the PR's share_token/
share_created_at fields kept in the Session constructor, metadata list, and
to_dict). CI workflow (.github/workflows/tests.yml) intentionally NOT taken from
the stale branch — master owns it (the branch's apparent removal of Playwright/
Office steps was a PAT-workflow-scope revert; rebase restores master's CI).
Co-authored-by: MicoRobot. Concept approved by maintainer (opt-in public share,
2026-07-11). Note: 4 dynamic share tests fail identically on the original
unrebased head (pre-existing test-env Session.load fragility, not rebase-caused);
to be resolved during the security re-gate in a proper sandbox.
Two non-blocking UX improvements from the Fable gate on #5739:
1. Full date+time tooltip (title attr) on the worklog event-time label — the bare
clock is date-ambiguous when a settled session is reviewed later or a run crosses
midnight. New _activityFullClockLabel (range-guarded so a bad epoch never yields
'Invalid Date' in the tooltip) + test.
2. Hide the time label under 420px — its ~50px of fixed chrome squeezes the
collapsed row's flexible preview toward zero on phones.
Co-authored-by: rodboev.
2026-07-11 00:38:23 +00:00
nesquena-hermesandrodboev. Gate findings from adversarial Codex re-gate.
Two gate findings on the worklog-event-timestamp feature (#5700/#5739):
1. Thinking-row timestamps disappeared after settlement. Reasoning source events
carry no server timestamp; the live DOM showed a fallback time but the settled
scene row rebuilt with created_at:null. Now stamp created_at on the source event
the first time it's seen (messages.js apply path) and carry row.created_at
through snapshot hydration, so the timestamp survives live->settled.
2. A garbage huge numeric timestamp (e.g. 1e20) passed the finite/>0 checks and
rendered literal 'Invalid Date'. _timestampSeconds now rejects epochs outside
JavaScript's valid Date range. Added a regression test.
Co-authored-by: rodboev. Gate findings from adversarial Codex re-gate.
Codex round-3 finding #2 [CORE]: when reconciliation replayed the active run's
terminal event at the subscribe cutoff, the live queue's copy hit the cutoff/dedup
'continue' BEFORE the terminal 'break' check — so the handler never broke, stayed
blocked on the dead run's queue, and missed subsequent session runs. Check the
terminal event type before the already-delivered continue: a terminal always ends
the loop (after emitting if not already sent). Regression test asserts the handler
unsubscribes (doesn't hang) when the terminal is deduped. 24/24 SSE tests green.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Codex round-3 finding #1 [SILENT]: baselining after end_sse_headers() left a window
where a run completing between header commit and baseline was absorbed into the
baseline + silently lost. Move resume-id parse + fingerprint baseline before
send_response/headers (both side-effect-free: header read + stat-only fingerprint).
Codex round-3 also flagged finding #2 [CORE] (reconciliation terminal-event-at-cutoff
dedup keeps the handler attached to a dead run queue) — NOT addressed here; escalating
to Nathan per Codex-depth diminishing-returns (3rd round into the same SSE seam class).
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Codex re-gate found the baseline was still after the preliminary
_active_run_stream_for_session() lookup + the initial cursor read_session_run_events()
replay — a run completing during either window folded into the baseline and was
silently missed. Move the fingerprint baseline to immediately after resume_event_id
parsing, before both operations, so no pre-baseline completion window remains. Remove
the now-redundant later baseline. 23/23 SSE tests green.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Final-gate Codex caught a TOCTOU race in the prior idle-seam fix: the fingerprint
baseline was captured AFTER the first attach_active_stream() lookup, so a run
completing DURING that first attach folded into the baseline and was silently
missed (only : keepalive emitted, no snapshot). Move the baseline capture before
the first attach so any completion from that point on is detected. Add an ordering
regression test (baseline fp precedes the idle-loop attach + advance still emits
one snapshot). 23/23 SSE tests green.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Fable gate found a seam Codex's 3 boundary findings didn't cover: an idle no-cursor
subscriber to /api/sessions/{id}/events misses a run that starts AND finishes inside a
single keepalive tick — the wait loop only looked for a live in-memory STREAMS entry to
attach, never noticing the journal advanced, so the run's transcript was silently missing
until manual refresh.
Add a cheap bounded session_journal_fingerprint() (file-count/max-mtime/total-size from
stat only, never parses bodies) and, inside the idle attach-wait loop, emit a session_snapshot
re-sync whenever the fingerprint advances with no live stream to attach — the same honest
recovery contract used for a failed reconciliation. Re-baselines after each re-sync so a quiet
idle connection never spams snapshots. 2 regression tests; 22/22 SSE tests green.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
Codex re-gate caught 2 defects in the prior UX commit:
1. streaming.py:8921 — the disclosure fired for ANY process_wakeup failure (e.g.
rate-limit) even when no pause was recorded -> falsely claimed 'retries paused'
while process_wakeup_pause stayed empty. Now gated on the record_* return value.
2. streaming.py:10130 — the raised-exception credential_pool_empty path (exercised
by the existing agent test) recorded the pause but omitted the disclosure. Added
the same conditional disclosure there.
Both paths now keep _error_payload['hint'] in sync with the persisted bubble, and
the disclosure appears only when a pause was actually recorded. 69 tests green.
Fable UX-gate follow-up on #5732: after the first credential-exhausted error,
automatic wakeups are silently suppressed. Disclose it in the wakeup-source
error card ('Automatic retries for this conversation are paused until you send
a message, switch the model/provider, or fix the credentials.') so the silence
reads as intentional, not a stuck agent — Gmail/Dropbox account-needs-attention
convention. Scoped to the process_wakeup + credential_pool_empty seam only.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
The pricing3-null case failed on one CI shard with StopIteration: pytest-shard's
partitioning interleaves this test with modules that mutate config.cfg away from
the openrouter active provider, so get_available_models() omits the openrouter
group and next(...) raised. Make the group lookup resilient (next(..., None) +
pytest.skip) — the exact defensive guard the sibling live-fetch tests in this
file already use for the same pollution. Passes both with and without hermes_cli.
Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>
Gate review (Codex) reproduced two SILENT regressions in the generation-token
ownership change, both validated against PR head + merge base:
1. sessions.js:1428 — the no-op early-return did NOT fire for a redundant
same-session load (currentSid===sid, !_loadingSessionId was false because a
keep-stale refresh was in flight). The redundant loadSession() then bumped
_loadSessionGeneration and CANCELLED the authoritative in-flight refresh —
stale rows remained while message_count advanced, so later polling could not
detect them. Restore the pre-existing behavior: also return when the in-flight
load is for the SAME sid (!_loadingSessionId || _loadingSessionId===sid).
2. sessions.js:2781 — _ensureMessagesLoaded's finally cleared the
transcript-width reload hint unconditionally, so a stale same-session response
collapsed a long session to the default tail window. Guard the cleanup with
_ownsLoad() so only the owning load clears the hint.
Both fixes retain the PR's cross-session, same-session-rejection, and INFLIGHT
race behavior (47 tests pass incl. the new isolation suite + the 4 edited files).
Co-authored-by: starship-s <starship-s@users.noreply.github.com>
The new test_openrouter_free_tier_pricing_fails_closed did an unguarded
'from hermes_cli import models', but hermes_cli (the agent package) is an
optional dependency not installed in the WebUI CI test environment, so the
test failed with ModuleNotFoundError on 4-5 shards (master green, PR head red).
Wrap the import in the same try/except guard the sibling tests in this file
already use — when hermes_cli is present we force its live-fetch to empty so
the fail-closed static path is exercised; when absent, that static path is
already the only one and the api/config.py fix under test still runs via the
mocked urlopen. Verified: 10/10 with hermes_cli, 5/5 with it blocked.
Co-authored-by: igorroncevic <igorroncevic@users.noreply.github.com>