fix(chat): a provider that cannot steer queues mid-turn messages instead of faking delivery - #10
Merged
Merged
Conversation
…l tool output The omp provider rendered a bare timeline: thinking was discarded, tool rows flattened to generic labels with one-line 84-char output summaries, inline diffs were dropped on the wire, and subagents never reached the Agents panel because omp's subagent frames are RPC-mode-only. Server: - OmpAdapter buffers agent_thought_chunk segments and settles them as reasoning items; ingestion appends them as reasoning.completed activities. - The wire projection keeps a capped multi-line rawOutput.fullText and the ACP diff content blocks on tool.completed rows (tool.updated stays slim), plus a bounded toolInfo (device/tool name, action, scalar args, eval code) derived from rawInput — retroactive for already-persisted threads. - omp's task tool calls synthesize the full subagent lifecycle from the TaskToolDetails streamed in rawOutput: task.started per subtask, deduped task.progress with intent/tool/usage/model, and task.completed driven by per-subtask progress status — the async-job mode acks the call as completed while agents still run, and the runtime evicts completed calls from its merge map, so the roster is cached per toolCallId and agents outliving the turn are marked backgrounded instead of spinning forever. - AcpSessionRuntime gains an opt-in emission predicate so omp task progress in rawOutput survives the title/detail update-suppression gate. - Informative provider titles (omp per-call intents) win the row heading over flattened action labels, and update frames no longer overwrite them with the bare "Tool" fallback. Web: - Thinking rows render italic/dimmed and expand to markdown. - Expanded tool rows show the full output, per-file FileDiff blocks built from the preserved diff contents, structured tool args, syntax-highlighted eval code, and markdown LSP results; icons key on the tool identity. Verified live against a real omp session: thinking, intent titles, inline diffs, and the Agents panel roster with per-agent status, intent, model, token and tool counts. Built with Claude Fable 5 on Oh My Pi.
…chat # Conflicts: # apps/web/src/components/chat/MessagesTimeline.tsx
…-looking working timer A thread sat at 'Working for 10m' with zero output while the provider (omp) retried upstream 529s internally forever without sending a single ACP frame. Nothing in the UI distinguished that from real work. Server: - AcpSessionRuntime stamps every inbound native frame (session/update before any filtering, permission requests, elicitations) and exposes a liveness snapshot with the in-flight tool-call count. - OmpAdapter runs a 15s watchdog per session: when a running turn has had no frame for 75s — observed retry cadence was 60-105s, normal streaming gaps are seconds, worst-case first-token latency stays under ~60s — it emits a transition-only session.state.changed carrying providerQuietSince, and a clearing transition when frames resume. An open tool call, a pending approval, or a pending user-input question suppresses the flag: those wait on the tool or the human, not the provider. Every sendTurn resets the silence baseline. - OrchestrationSession gains optional providerQuietSince, persisted through a new projection column so a client opening the thread mid-stall sees the flag in the snapshot — exactly the incident scenario. getSnapshot also had a latent inline session mapping that silently dropped unknown fields; it now goes through mapSessionRow. Clients: - Web swaps the working row for 'No output from the provider for Xs · last activity HH:MM:SS — still waiting; you can stop the turn' (same self-ticking text-node pattern, one commit per transition). Stop stays in the composer. - Mobile renders the same truthful label, and — audit finding — now shows session.lastError in the chat at all: a compact error card above the composer. Web already surfaced it via ThreadErrorBanner. Deterministic tests: quiet-transition resolver matrix, ingestion mark/clear/ settle round-trip through the real SQL projection, and native-frame stamping via the mock ACP agent. Verified live by SIGSTOPping the provider mid-turn (flag appeared at +85s with the freeze timestamp) and resuming (flag cleared, turn settled). Built with Claude Fable 5 on Oh My Pi.
…ead of faking delivery
Messages sent while an omp turn ran appeared in the timeline as delivered,
but only reached the agent after the turn settled. omp over ACP has no
steering surface: a concurrent session/prompt implicitly cancels the running
turn, and T3's prompt serialization (which prevents exactly that cancel)
holds the message invisibly until the in-flight prompt returns.
The provider snapshot now carries midTurnSteering ('native' | 'queued',
absent means native so legacy producers are untouched), omp declares
'queued', and the web composer routes mid-turn sends on such providers to
the follow-up queue: the message shows up as a queued card — visible,
editable, reorderable — and the queue reactor delivers it the moment the
turn genuinely ends, which is when it would have arrived anyway. The
effective follow-up behavior swaps steer→queue so the button and chord
affordances tell the same truth, and the resolver enforces it for the
explicit send-now chord too. Interrupt is unchanged: stop plus a fresh
prompt needs no steering.
Real mid-turn steering for omp needs an ACP extension mirroring its
RPC-mode streamingBehavior; that is an upstream follow-up.
Built with Claude Fable 5 on Oh My Pi.
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem (reproduced live, twice — the second bug report WAS the bug)
Messages sent while an omp turn ran appeared in the timeline as delivered, but the agent only received them after the turn settled — minutes later. The reporter's duplicate follow-up arriving post-turn was itself an instance of the defect.
Root cause chain:
followUpBehavior: "steer"dispatchesthread.turn.start, which immediately writes the user message into the timeline.session/prompt— which blocks onAcpSessionRuntime's prompt-serialization semaphore until the in-flight prompt RPC returns.session/promptimplicitly cancels the running turn (ompacp-agent.tsprompt handler). Real steering (streamingBehavior: "steer") exists only on omp's RPC transport.So for omp, "steer" was a silent queue wearing a delivered-message costume.
Fix
Report truthfully instead of pretending:
ServerProvider.midTurnSteering?: "native" | "queued"(same pattern asshowInteractionModeToggle; absent = native, so every legacy producer is untouched). omp declares"queued"with the rationale in code.resolveFollowUpDeliveryenforces the rule for the explicit send-now chord too.interruptis unchanged — stop + fresh prompt needs no steering and remains the immediacy option for omp.Verification
resolveFollowUpDeliverymatrix: default steer → queue, explicit send → queue, interrupt intact, idle sends intact, native providers untouched.midTurnSteering: "queued".Follow-up (upstream, not this repo): an omp ACP extension mirroring RPC-mode
streamingBehavior: "steer"would enable real mid-turn steering; this change makes the transport limitation visible instead of hiding it.Built with Claude Fable 5 on Oh My Pi.