With unifiedSession enabled, all turns are routed to the shared
unified:default session, so title generation, persistence, and change
notifications all happen on that shared session. The WebUI sidebar,
however, renders per-chat websocket:<id> sessions, so generated titles
never reach the session the user sees and every chat stays "Untitled".
Pass the origin chat_id through maybe_generate_webui_title_after_turn and
add a target_session_key to maybe_generate_webui_title so the title is
written to the per-chat session while conversation content still comes
from the shared (routed) session. When the routed session differs from
the per-chat session, the latest user/assistant exchange is used as the
title prompt since the shared session mixes content from every channel.
The shared session's title metadata is no longer read or written, so
stale shared titles can no longer leak into per-chat sessions.
- advance the disabled-dream cursor before touching the cron store so
the #4242 fix still applies when jobs.json is corrupt
- pin the gateway wiring: the disabled-path test now asserts both
dream and heartbeat are retired, not just unregistered
- cover remove_system_job on a fresh install with no store file
Disabling gateway.heartbeat (or agents.defaults.dream) only skipped
job registration on startup; the previously persisted system job in
<workspace>/cron/jobs.json kept firing, and remove_job refuses to
touch protected system jobs. Add CronService.remove_system_job for
startup reconciliation and call it from the gateway disabled branches
so the config toggle takes effect after restart.
Mid-stream response.failed events from OpenAI carry
'server_error' (underscore), but the transient-error marker
list only had 'server error' (space). is_transient_response
missed it, so the retry loop gave up on the first attempt
instead of using its normal 1s/2s/4s backoff.