fix(ui): chat send failures were invisible when the session pane was …

fix(ui): chat send failures were invisible when the session pane was hidden (#124473)

* fix(ui): always surface terminal chat send failures

Terminal chat send failures were recorded only on the queue item when the
owning pane was not visible (reconnect, alias drift, split-pane routing),
so the operator saw nothing at all — an action ending in silence.

Root cause: every error surface in the send/drain path was gated on
visibleSessionMatches(...), and the FIFO outbox drain treated every
chat.history reconcile rejection as a silent retryable "blocked", so a
non-retryable rejection (e.g. auth loss) wedged the head — and everything
behind it — permanently with no visible outcome.

Fix, at the owner:
- New surfaceChatDeliveryFailure() in steer-lifecycle.ts (the shared
  error-text owner): visible pane keeps the inline chat error; otherwise
  the failure routes through the existing global toast host, naming the
  session. All terminal failure sites in chat-send-delivery,
  chat-outbox-drain, chat-send-queue-state, and steer-lifecycle now use it.
- reconcileStoredChatOutboxHead: a non-retryable GatewayRequestError on the
  head now terminally fails a never-attempted head (unblocking the lane)
  or parks an attempted head as unconfirmed — both with a visible outcome —
  instead of blocking the lane forever.
- Composer disabled-reason: the reason now also renders while a draft hides
  the placeholder, and the cloud-startup-pending gate gets a reason instead
  of a silently disabled composer.

Regression tests: hidden-pane terminal failure surfaces via toast; wedged
head fails visibly and the lane drains the next message; attempted head
parks unconfirmed; disabled reason visible with draft text present.

* fix(ci): raise startup JS baseline for global failure surfacing and pin workspace-sync test clock

The chat send path now imports session-display naming for the global
failure toast, adding ~1 KiB gzip to startup JS (335452 B on CI's Linux
builder, still well under the 358400 B committed cap).

workspace-sync "never commands" asserted the exact dispatch timeoutMs
(777) but the impl derives it from a Date.now() deadline, so any elapsed
ms between admission and dispatch failed the exact-equality assertion on
a loaded runner. Pin the clock like the sibling timeout tests do.

* fix(ui): surface route-switched command failures and agent-scope global toast naming

ClawSweeper review findings on #124473:

- A queued local command failing after the operator navigated away hit
  failCommand(error) with expose=false; the dispatcher's stale-scope
  guard had already withheld the inline error, so a successful state
  write recorded the failure invisibly — the silent class this PR
  removes. Expose it globally when the scope is stale and the owning
  pane is hidden; a stale scope with the pane still visible keeps the
  failed queue chip (the new connection owns the inline surface).
- Global session rows are agent-scoped behind one shared "global" key,
  so the toast row lookup could borrow another agent's label. Match the
  row's agentId to the failed outbox's agent for global keys.

Both regression tests fail pre-fix (stash-verified).
添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论