The idle tick queued the started admission's text, and run_conversation
pumped it only when the text it got still matched. The input loop and
chat() clean the text first (leaked CPR or mouse reports, paste markers,
lone surrogates). A queued prompt with any of those no longer matched, so
run_conversation sent a new prompt.submit while the owner was already
running the queued one, and the prompt ran twice.
The seeded item now carries the admission id. When the input loop takes
it, that id is armed on the session state, and run_conversation pumps
exactly that admission. A typed input with the same text is its own
submit.
Refs #134889 (N5)
On main the classic CLI drained tools.process_registry itself and ran the
`[IMPORTANT: Background process ...]` turn, painting `◈ Background Process
Finished: <cmd>` and the reply box. Over the session authority the process
runs in the gateway, which drains the completion and admits/runs the
notification turn itself; the CLI's registry is empty and its frames arrived
between CLI turns, to be discarded by the next turn's stale drain. The
viewer showed nothing.
- The claim's `message.start` now carries an automation admission's
async-result card (display_kind / display_text / model text,
session_automation.admission_display); ordinary claims stay `{}`.
`message.start` gains an all-optional payload contract.
- A gateway-backed CLI's idle / post-turn tick no longer drains the process
registry or claims delivery (IPS-086): it watches its session's stream for
a carded `message.start` and queues main's TimelineNotification (ahead of
inputs queued behind the running turn), so main's input path renders the
same header and rule.
- GatewayAgent.run_conversation adopts that already-admitted turn (no
prompt.submit) and pumps its frames through main's callbacks into a
run_conversation-shaped result, so main's response box renders.
(cherry picked from commit 4bf4cc03445bcfdcdfced4f141443bbab8ad5eb2)
A `terminal(background=true, heartbeat=N)` tick queued a notification every N seconds
whether or not the process had printed anything, and every queued event costs the owning
session a full model turn. On Desktop and the TUI that turn painted the wake as a user
bubble ("[Background process ... heartbeat #9 ... (no new output since the last
heartbeat)]") followed by the model's "Still running normally." — over and over, for a
process whose row on the status stack already said it was running — and while the wake
held the session's turn, the user's own prompt sat queued behind it.
- `ProcessRegistry._emit_heartbeat` skips a tick with no new output. The sequence counts
delivered beats only; the "(no new output)" placeholder in the formatter is gone.
- TUI/Desktop type heartbeat rows `display_kind: hidden` (the kind both clients and the
transcript preview already honour); the CLI paints a one-line receipt and persists the
row hidden, so reopening the session in Desktop shows only the agent's reply.
- Desktop hydration drops heartbeat rows persisted by older backends the same way.
- `display.background_process_notifications: off` is honored by the TUI/Desktop poller and
the CLI drain, not just the messaging gateway. `off` mutes process-driven wakes only:
a finished `delegate_task(background=true)` still lands.
Supersedes #123123 (cherry-picked; scoped so `off` keeps subagent results) and #119202
(cherry-picked; `heartbeat: 0` is schema-valid so models that materialize every field
stop tripping the foreground guard).
The documented `off` mode gated only the gateway's completion injection
(#9290); the CLI drain never consulted the key, so setting it silently did
nothing on the CLI — leaving no escape hatch against the post-Ctrl+C
notification turn restarts (#123114).
Mirror the gateway semantics: the drain still claims and acknowledges
completion events (durable rows converge instead of replaying on restart),
but the turn-starting `_pending_input` injection is suppressed.
Fixes#123114
(cherry picked from commit d009992c9e64b6083951b27a8261d138ae669ccb)
Squash of the 54 commits on victor-kyriazakos:feat/user-channel-warning-suppression
(PR #112302, head f45c640e55) so the contributor's authorship survives a rebase-merge;
the commits interleave with a cron delivery-ledger rework that the salvage removes in
follow-up commits, so per-commit cherry-picks were not practical.
Adds display.suppress_warning_notifications (global + per-platform, default false):
one resolver (gateway/warning_notifications.py), BasePlatformAdapter.emit_warning /
emit_media_warning / warning_text, a notification_category classification carried
through wakes, queues and persistence, and render/present boundaries for CLI/TUI.
Subagent completions already got this: the model receives the full
`[ASYNC DELEGATION …]` text while the CLI/TUI/Desktop paint a one-line
"Subagent Task Completed: <goal>" event. Background-process completions
(`terminal(background=True, notify=True)`) still echoed the entire
`[IMPORTANT: Background process proc_… completed normally (exit code 0).
Command: … Output: …]` block as if the user had typed it.
Generalise the delegation mechanism: `TimelineNotification` (formerly
`SubagentNotification`) carries `display_kind` + `display_text`;
`ProcessNotificationBatch` renders a `process_complete` one with a
`process_completion_display_text` title ("Background Process Finished:
<cmd>", "Background Process Failed (exit 1): <cmd>", "N Background
Processes Finished"). The TUI gateway stamps the same kind/metadata on
the synthesized turn and emits the title on `status.update`; Ink and
Desktop project `process_complete` rows as timeline events (Desktop keeps
the raw output behind the existing expandable async-result row). Model
content is byte-identical to before.
Based on the earliest batching proposal by BrunoBza (#104686) and JoaoMarcos44 structured correction (#104703). Slim redo into topical siblings and shared TUI poller/post-turn routing. Reported by Xipong (#104671). Local shell-to-loopback probes demonstrate 12 to 1 turn dispatches; suites await the campaign test lock.
Co-authored-by: joaomarcos <joaomarcosdias444@gmail.com>