fix(desktop): self-retry transient boundary errors, reactive edit composer context - #72867
fix(desktop): self-retry transient boundary errors, reactive edit composer context#72867Adolanium wants to merge 1 commit into
Conversation
…poser context Two correctness holes left by the session-switch perf work (NousResearch#72504 / NousResearch#72524): 1. MessageRenderBoundary only cleared a swallowed transient useClientLookup error when the structural resetKey changed. Mid-turn, ids/roles/count are stable, so a lookup race during a stream left the boundary rendering null for the rest of the turn. The boundary now self-retries on a 0ms timer (rAF never fires in a parked renderer), bounded to 5 consecutive transient catches with the budget reset on recovery; the structural resetKey path is unchanged, and non-transient errors still re-throw. 2. cwd / gateway / sessionId were removed from the messageComponents memo deps and read through a render-time ref so session switches stop reminting the component types. But a mounted UserEditComposer only reads that ref when it renders, and a same-session change (cwd remap, gateway reconnect) leaves every ThreadMessageList prop referentially equal, so the memo'd list bails out and the open composer keeps stale values: @-completions, slash completions, and OS-drop uploads target the old cwd / gateway / session. Thread now provides the three values through a memoized ThreadEditContext; context propagates through the bail-out, the component type identity is untouched, and the transcript never remounts.
|
Thanks for the focused regression fix. Current The proposed bounded retry preserves non-transient rethrows and the context provider preserves the stable component-map identity. The regression tests target those two behaviors, including the no-transcript-remount invariant. The PR base is an ancestor of current Automated hermes-sweeper review. |
SummaryOne PR addresses #72866. #72867 covers both reported root causes by adding bounded self-retries for transient lookup failures and reactive edit-context propagation for an already-open composer, while preserving stable transcript component identity. Related pull requests
Suggested consolidationKeep #72867 open with a salvage path: retain both the bounded transient-error retry and the reactive ThreadEditContext change together with their regression tests. It is the only PR in this complex, directly addresses both independent causes in #72866, and has no competing PRs to close as duplicates. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I72866(["issue #72866 (open)"])
P72867["PR #72867 (open)"]
P72867 -->|best fix| I72866
class I72866 open
class P72867 open
class P72867 best
class P72867 target
click I72866 "https://github.com/NousResearch/hermes-agent/issues/72866"
click P72867 "https://github.com/NousResearch/hermes-agent/pull/72867"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 22 kB of PR diffs, 11 kB of issue/PR text, 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
|
Superseded by #81232. Consolidated with #64310 into one PR: both fix the same Both of your fixes carried over unchanged — the bounded self-retry and the reactive |
What does this PR do?
Fixes two correctness holes left by the session-switch perf work (#72504 / #72524), without giving back any of the perf wins.
1. Transient boundary errors can blank a turn mid-stream.
MessageRenderBoundaryswallows the transientuseClientLookup ... out of boundsrace and renders null untilresetKeychanges. #72504 narrowedresetKeyto pure structure (right call, it stopped per-token reconciles of every turn), but that removed the implicit recovery: mid-turn the structure is stable, so a race during a stream left the turn blank until an unrelated message add/remove. The boundary now self-retries on a 0ms timer (rAF never fires in a parked renderer), bounded to 5 consecutive transient catches with the budget reset on successful recovery. A persistent failure falls back to the old wait-for-structure behavior, and non-transient errors still re-throw to the root boundary.2. An open edit composer keeps a stale cwd / gateway / sessionId. #72524 moved the three values out of the
messageComponentsmemo deps into a render-time ref so session switches stop reminting the component types. That keeps them fresh for a composer that mounts later, but a composer that is already open never re-reads the ref: a same-session change (the agent relocating the session's cwd viasession.info, a gateway reconnect) leaves everyThreadMessageListprop referentially equal, so the memo'd list bails out.@-completions, slash completions, and OS-drop uploads then act on the old context. Thread now provides the three values through a memoizedThreadEditContext. Context propagates through the bail-out, the component type identity is untouched, and the transcript never remounts.Related Issue
Fixes #72866
Type of Change
Changes Made
apps/desktop/src/components/assistant-ui/message-render-boundary.tsx: bounded timer self-retry for transient lookup errors, budget reset on recovery, timer cleared on unmount and on structural reset.apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx: 4 new tests (timer recovery without a resetKey change, retry cap, budget reset across streaks, no retry for non-transient errors). Existing resetKey and re-throw tests untouched.apps/desktop/src/components/assistant-ui/thread/index.tsx:editContextRefreplaced with a memoizedThreadEditContextprovided around the list; theUserEditComposerwrapper reads it viauseContext. Comments updated to explain why neither memo deps nor a ref can carry these values.apps/desktop/src/components/assistant-ui/thread/edit-context.test.tsx: new. Same-session cwd change reaches the mounted composer (fails on main), session-switch control, and a perf invariant guard proving a cwd rerender does not remount any transcript DOM node.How to Test
npx vitest run --project ui src/components/assistant-ui/message-render-boundary.test.tsx src/components/assistant-ui/thread/edit-context.test.tsxnpx vitest run --project ui src/components/assistant-ui/npm run typecheckclean.npx eslinton the touched files clean.Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) - or N/A (code comments on both fixed paths)cli-config.yaml.exampleif I added/changed config keys - or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows - or N/AScreenshots / Logs