fix(tui): drop cross-session events during null-sid switch window (#51058) - #53928
fix(tui): drop cross-session events during null-sid switch window (#51058)#53928yingliang-zhang wants to merge 4 commits into
Conversation
no_agent cron jobs short-circuit run_job() before any SessionDB work,
so they never produce the cron_{job_id}_{timestamp} session row that the
run-history endpoint (GET /api/cron/jobs/{id}/runs, backed by
SessionDB.list_cron_job_runs) is built from. Manually triggering such a
job from the Desktop GUI gives zero feedback: no running indicator, no
run record, no output — the script runs fine, the GUI just can't see it.
Fix: in the no_agent branch of run_job(), create the run session
(cron_{job_id}_{ts}, source='cron') BEFORE executing the script so the
runs endpoint's is_active computation yields a running indicator for
in-flight manual triggers. After execution, persist the outcome doc as
an assistant message, title the session, and end_session. Covers all
four exit paths: success, script failure, empty-stdout silent run, and
wakeAgent=false silent run.
Best-effort: a missing/broken state store degrades to the old no-record
behaviour and never blocks the script run. The no_agent cost contract
is preserved — run_agent/AIAgent are still never imported on this path.
Supersedes NousResearch#44087 (same approach, rebased on current main).
Fixes NousResearch#44080, NousResearch#42433.
… consecutive user/user turns Local fix (not yet PR'd). After compression, the preserved todo list was appended as a standalone user message; when the compressed transcript already ended with a user message this produced consecutive user/user turns — a content-ordering violation some providers reject. Now fold the snapshot into the trailing user message (blank-line separated) instead of appending a second standalone user turn. Falls back to append when the tail is empty, non-user, or structured (list) content so image/tool parts are not corrupted.
…usResearch#51058) The event filter in createGatewayEventHandler.ts used `sid &&` as a guard, which evaluated to false when sid was null (during resetSession() in activateLiveSession/resumeById). This bypassed the ENTIRE filter, allowing events from other live sessions to bleed into the active view. Change `sid &&` to `(!sid ||` so that when sid is null, ALL non-gateway session events are dropped instead of let through. This closes the race window where two concurrent live sessions can cross-contaminate. Repro: two TUI sessions active simultaneously, one undergoes context compression (rotation mode). After compression, the other session's content appears in the first session's view. DB audit confirms data is correctly isolated — the bug is purely in the TUI event filtering layer.
Heads up for reviewers: the title/body describe a 1-line TUI event-filter fix (
Recommend splitting into focused PRs (or at least correcting the title/scope) so each change can be reviewed on its own. Related: #48525, #48524, #51058. |
|
Closing — this PR was opened from a |
Problem
Closes #51058 (TUI/Desktop session mix-up after context compression / reconnect resumes the wrong chat).
When two TUI sessions are active simultaneously and one undergoes context compression (or any session switch occurs), the other session's messages can bleed into the active view. A full
state.dbaudit confirms the data layer is correctly isolated — the bug is purely in the TUI frontend event filtering layer.Root cause
The event filter in
createGatewayEventHandler.tsusedsid &&as a guard:When
sidisnull(duringresetSession()inactivateLiveSession/resumeById),sid &&evaluates tofalse, bypassing the entire filter. ALL events from ALL live sessions pass through, causing cross-session content bleed.Fix
Change
sid &&to(!sid ||so that whensidisnull, ALL non-gateway session events are dropped instead of let through:This closes the race window: during session switch, no session-specific events are processed until the new
sidis set.Safety
gateway.*events always pass (they carry no session-scoped data)session_idalways pass (session-agnostic)sid: nullwindow inresetSession()is synchronous (nanostores), so no events are actually processed during it in normal execution — this fix is belt-and-suspenders defense for edge cases (React flushSync, microtask boundaries)Verification
tsc --noEmitpassesnode scripts/build.mjspassesresolve_resume_session_id,get_compression_tip,get_messages_as_conversation, andlist_sessions_richall return correctly isolated data — the bug was exclusively in the frontend filter