fix(desktop): derive conversation execution from Host snapshots - #5217
Conversation
Generated-by: OpenAI Codex
Generated-by: OpenAI Codex
Generated-by: OpenAI Codex
Generated-by: OpenAI Codex
Generated-by: OpenAI Codex
Separate observable activity from retained Stop targets in Side Chat and WorkHub. Remove unused snapshot surface and make observation type imports explicit for Knip. Generated-by: OpenAI Codex
Allow repeated pending invalidation during failed Host restoration while requiring the observation error and excluding synthetic Runtime events. Generated-by: OpenAI Codex
Sun-GLiang
left a comment
There was a problem hiding this comment.
Design verdict. Reusing the existing Host observation channel, shared execution projection and TurnView is the appropriate direction. Four P2 behavior gaps remain in content handoff, compaction Stop and unavailable-state presentation, plus one P3 cleanup of paths whose production consumers were removed. The five findings are attached inline; I would address the P2 items before merging.
AI-assisted review of 97bcd881c786e1c8964e0d3e40f59288702a5b42. Its only change since the reviewed 7012fc29f is the restored-observation test; all five affected production locations are unchanged.
Verification. 126 existing focused tests passed against the reviewed source. Additional local reproductions exercised the mounted content reconciler, the actual Side Chat hook, a held Host-subscription recovery and ChatView rendering. Changing only the content reconciler to the existing whole-buffer selector fixes the late-seed reproduction. These checks did not include a full build or Electron end-to-end rerun. The latest test check is still running at submission time; the previous pending-count test failure has been addressed by the new test-only commit.
中文
设计结论。 复用现有 Host 观察通道、共享执行投影和 TurnView 的方向合理。目前仍有 4 项 P2 功能问题,涉及内容交接、压缩停止和观察不可用时的展示,另有 1 项 P3 无用代码清理;五项均附在行内。建议合并前解决 P2 项。
本次为 AI 辅助审查,对应 97bcd881c786e1c8964e0d3e40f59288702a5b42。相比已审查的 7012fc29f,最新提交只修改恢复观察的测试,五处相关生产代码均未变化。
验证。 已审查源码的 126 项现有定向测试通过;另外使用实际挂载的内容清理器、Side Chat hook、延迟恢复的 Host 订阅和 ChatView 渲染复现问题。仅将内容清理器改为使用已有全缓冲选择器,迟到 seed 的复现即恢复正常。本轮未执行完整构建或 Electron 端到端复跑。提交时最新 test 检查仍在运行;此前 pending 数量断言的失败已由新增的测试提交处理。
|
I think this still needs changes before merge. I found five items: two correctness issues in the multi-Turn handoff, one clear simplification opportunity, and two presentation cleanups.
The latest test-only commit does not change the first three code paths above. |
Scope readiness to the Composer submit control and wait before consecutive queued and steering submissions. Generated-by: OpenAI Codex
Reconcile the whole live buffer, bind compaction Stop requests to Host turns, invalidate observations when recovery starts, and gate retained activity presentation on current Host observation. Remove unused migration interfaces and place reconciliation in the Conversation feature. Generated-by: OpenAI Codex
Regenerate both Astryx surface inventories for the Conversation-owned reconciler path. Generated-by: OpenAI Codex
Resolve overlapping E2E readiness fixes by retaining the Composer-scoped submit selector and shared admission wait. Generated-by: OpenAI Codex
zhiiw
left a comment
There was a problem hiding this comment.
Scoped review (conversation execution projection, turn-buffer handoff, observability, and the removal surface), at exact head 396cc49f (verified unchanged at publish time; test completed/success on this head; mergeable). This covers one assigned slice of the PR, not the whole diff — the aggregate review is coordinated elsewhere.
No P0–P2 in this scope. One P3 below.
Turn-buffer handoff. The boundary is packages/ui/src/live-turn-buffer.ts: content is retained per turnId and released only when the durable assistant evidence for that turn arrives (reconcileLiveTurnBuffer, including the late-callback recovery loop for terminal turns). During a handoff, the old turn's content stays on the old turn (applyLiveTurnBufferEvent routes by turnId; a newer snapshot cannot overwrite it), while the running state and Stop target belong to the new turn (tailTurnId in chat-view.tsx comes only from the Host-snapshot active turn — no tail fallback). Between Enter and Host admission the user sees the message delivery row, not a fake spinner — a deliberate honesty-over-immediacy change, covered by the design doc's acceptance table.
Observability loss. Every activity cue is availability-gated, verified site by site: the main-chat answer footer (chatTurnActivity returns undefined when available is false — application/contracts/session-execution.ts:32), the turn-internal tool timeline (activityObserved reaches tool-activity.tsx's running/isPulsing via ProcessingBlock/TurnTimelineEntry), the compaction row (shows the unobserved copy instead of a spinner, chat-turn.tsx:610-617), the sidebar pulse (selectStreamingSessionIds gates on execution?.available first), and the WorkHub/Side-Chat surfaces (same gate). The Stop target deliberately survives unavailability (activeHostTurn filters terminal states only), and the Host-side command matching still rejects a turn that ended while unobserved. Observation failures now travel as host_observation_error on the observation channel instead of synthesizing Runtime terminal events, and setExecution marks the retained snapshot available: false rather than dropping it.
The removed 616 lines. Every deleted mechanism was checked for residual references on the merged-with-main head (this matters: main brought 107 files in via the merge, so the check was re-run there): the model-wait classification, delayed-flag cues, settled-session transient cleanup, the message-id-as-turn arm/rebind/disarm machinery, confirmLiveTurn, and catalog-driven transient cleanup are all gone with zero callers — including from main's newly added surfaces, none of which call into the narrowed paths. Interaction teardown narrowed from session-wide to turn-scoped (clearInteractions takes turnId; BaseEvent.turnId is a required string, so the wipe-all fallback is unreachable). The new structures are four small single-purpose modules; I found nothing further removable.
[P3] apps/desktop/src/main/__tests__/fake-dom.ts:80 still carries a comment referencing the deleted useDelayedFlag ("schedules the turn-wait cues through the window") — the module is gone, and the comment will mislead the next reader. (The file is outside this PR's diff, so this is here rather than inline; it is a one-line deletion.)
Verification boundary, stated honestly: per the constraints of this review I read the full cumulative diff and traced the seams, but did not run the test suites locally; the hosted test job is green on this head, and the author's own E2E/Storybook evidence is cited, not independently reproduced by me.
Automated review notice: This comment was posted by an automated review agent operated by zhiiw. It is not an independent human review and does not replace one.
简体中文
范围审查(会话执行投影、Turn 缓冲交接、观察可用性、删除面),锚 exact head 396cc49(发布前复核未漂,test 绿)。无 P0–P2。交接边界在 live-turn-buffer:内容按 turnId 归属、durable 证据到达才释放;交接中旧轮内容不被新轮覆盖,运行状态与 Stop 目标归新轮;Enter 到 Host 承认之间显示交付行而非假转圈(故意的诚实优先行为变化)。观察不可用时所有活动提示逐处核实全部收敛(主对话底部、工具时间线、compaction 行、侧栏脉冲、WorkHub/SideChat 同一 available 门),Stop 目标故意保留,Host 侧匹配校验兜底。删除 616 行在并入 main 后的新 base 上重跑引用检查全部零残留(含 main 新进的 107 文件)。一条 P3:fake-dom.ts:80 注释引用已删的 useDelayedFlag(文件不在 diff 内故写在此处)。验证边界如实:按本线约束只读全 diff 追缝,未本机跑套件,hosted test 绿,作者的 E2E/Storybook 证据为引用未独立复现。
zhiiw
left a comment
There was a problem hiding this comment.
Approving at exact head 396cc49f (verified unchanged at approval time; test completed/success; mergeable).
This approval stands on my own scoped review (published as the preceding COMMENT): the conversation-execution projection, turn-buffer handoff, observability-loss behavior, and the removal surface, with no P0–P2 found there and one P3 (the stale useDelayedFlag comment in apps/desktop/src/main/__tests__/fake-dom.ts:80) noted. The coverage map, the verification boundary (full-diff reading and seam tracing, no local suite runs under this review's constraints, hosted test green on this head), and the migration-gap distinction are all in that review and remain valid.
It endorses no other reviewer's conclusion and does not address merge readiness outside that scope.
Automated review notice: This comment was posted by an automated review agent operated by zhiiw. It is not an independent human review and does not replace one.
简体中文
本 approve 基于我自己已发布的 scoped COMMENT(结论不变:范围内无 P0–P2,保留那条 P3)。覆盖范围、验证边界、迁移缺口区分都在那条里,仍然有效。不替另一位 reviewer 背书,不表态范围外的合并就绪性。
Deliver execution and content together through the observation stream and publish readiness only after consumption. Remove invoke seed replay and independent readiness IPC; address initial delivery to registered observers and preserve cancellation and recovery ordering. Generated-by: OpenAI Codex
jackwener
left a comment
There was a problem hiding this comment.
Approve at a98c5776daa5ead95efee6dea1f0a1ac3558b62c. This is a re-review: I reviewed an earlier revision of this pull request and raised the readiness-ordering problem that the latest commit addresses, so this is not a first-time independent reading.
Why the earlier ordering problem is now structurally absent rather than mitigated. Readiness, the execution projection and the accumulated content now travel as one message on the single ordered session event channel. The renderer applies the execution projection, then replays the seeded events, then publishes readiness, all within one synchronous block. The registration reply no longer carries events and no longer publishes readiness. There is no longer a second delivery path whose arrival order has to be reasoned about, so the previous failure could not be reconstructed on this revision.
Boundaries I checked on this commit.
- Initial activation ordering. Readiness strictly follows consumption in the same block; a seed that finds the subscriber already disposed stops before publishing readiness.
- Several subscribers on one window. First-delivery state moved from the window to the individual registration, and each seed names the registrations it is for. A new subscriber's seed is filtered out by existing subscribers, and the test suite pins exactly that.
- Joining during recovery. Recovery reuses the same seeding path for every registration on the window. A subscriber seeded by recovery while its own registration call is still suspended finds itself already recorded as delivered when that call resumes, so it is not seeded twice.
- Cancellation and failure. A failed subscription rejects the registration wait, detaches the observer and surfaces the error to the renderer's error path. The seed loop re-checks disposal on every step.
- Main-to-preload wiring. Both new observation messages are sent through the same channel helper as ordinary events and are part of the declared message union; the registration call's return type was narrowed to match a reply that no longer carries events.
- Removal of the renderer's generation guard. The protection did not disappear: the renderer still rejects callbacks from superseded attempts, and the preload layer only accepts a seed naming its own registration, which is freshly created per subscription. Because the pending and seed messages now share one ordered channel, a generation counter is genuinely redundant here rather than merely unused.
Ablation, and an honest limit on it. I checked each new assertion against the code path it replaces: the assertion that a registration acknowledgement publishes nothing, the exact execution-then-content-then-ready order, the refusal to replay older state on a late acknowledgement, the isolation of a new subscriber's seed from existing subscribers, and the recovery case naming several registrations. Each one contradicts a behaviour that the previous revision had, so none of them is vacuous. However, this is a reasoned ablation, not an executed one: I did not revert the fix and observe these tests turn red, so I can state that they could not pass against the previous behaviour, not that I measured them failing.
What I did not verify. I read code and hosted check results only; I ran no tests, build, end-to-end or Electron session. Real inter-process delivery ordering, timing windows and recovery races were therefore not measured. I traced the subscription owner's failure handling only along the paths that reach seeding and readiness, not its full retry behaviour under repeated establishment failures.
Gate state. The required check is terminal and successful on this commit. Note that the approval currently recorded against this pull request was submitted on 396cc49f, which predates the seeding rewrite; this approval is the one bound to the current head. No merge was performed.
Automated review notice. This review was produced by an automated reviewer (agent seat kabi-opus, model Claude Opus) acting on its own review identity. It is posted from a shared machine account, so the seat is named here because the account alone does not identify the reviewer. It is not an independent human review and does not substitute for one. Its scope and limits are stated above.
zhiiw
left a comment
There was a problem hiding this comment.
Scoped re-review (same slice as before: observation delivery, turn-buffer handoff, observability, and the three conversation surfaces' consumption boundaries) at exact head a98c5776 (verified unchanged at approval time; test completed/success on this head). This is my own second pass, not a restatement of anyone else's conclusion.
No P0–P2 in the delta. The previously noted P3 (the stale useDelayedFlag comment in fake-dom.ts) was explicitly accepted by the maintainer as wontfix and is not re-raised.
The initialization-order rework, verified rather than assumed:
- Readiness now follows consumption: the observe reply only confirms registration (
Promise<void>); the seed (execution + content events) travels as onehost_observation_seedmessage on the same ordered channel as live updates, andreadyis published only after the seed's events have been applied (preload.ts:2306area). The invoke-reply replay loop, the separate ready IPC, the oldonSeededentry, and the reply's generation parameter are all gone. - New subscriptions are isolated from old ones: the seed names its
observerIds, and preload drops seeds not addressed to this observer; per-registrationseededtracking (moved from the target group to the registration) means an observer admitted during recovery gets its own seed while already-seeded observers are not re-seeded, and a re-observe()of an existing observer waits for readiness and seeds only if unseeded. - Recovery reseeds through the same message: a replacement snapshot is delivered as a seed carrying terminal-then-active events, instead of raw broadcasts plus a separate ready emission;
host_observation_pendingon the same channel replaces the old seed-phase channel, so ordering between "stale" and "fresh" facts is the channel's own order. - The content/durable handoff is untouched: the buffer and reconcile paths from the prior round are unchanged; Side Chat's
onSeededconsumer is preserved through the workbar shim (readyphase →onSeeded), and WorkHub consumes the phase callback directly. - The dropped generation check in
live-content-seed.tsis justified by the new ordering guarantee (ready can only follow its own seed on the same ordered channel, and stale subscription attempts are already fenced by the attempt counter) — reasoning-verified, not test-verified by me.
Verification boundary, stated honestly: per this round's constraints I read the full delta and traced every seam, ran no suites locally, and rely on the hosted test job (green on this head) plus the author's lint/type/architecture checks. The initialization race windows (seed-vs-live interleavings under real reconnects) are exercised by the author's updated observer/preload tests, which I read but did not execute.
This approval covers this slice only and endorses no other reviewer's conclusion; merge remains for the coordinator once the other line closes.
Automated review notice: This comment was posted by an automated review agent operated by zhiiw. It is not an independent human review and does not replace one.
简体中文
范围复审(同一线 B:观察交付、缓冲交接、观察可用性、三个面的消费边界),锚 exact head a98c577(复核未漂,test 绿)。增量无 P0–P2;旧 P3 维护者已明确不修,不重提。初始化收拢核实:ready 跟随消费(seed 走同一有序通道,消费完才 ready;回包只确认注册),回包重放/独立 ready IPC/旧 onSeeded/旧 generation 参数全删;seed 带 observerIds,preload 丢弃不属于自己的 seed,注册级 seeded 使恢复期新订阅拿到自己的 seed 而旧订阅不重复;恢复重放也走同一 seed 消息,pending 与事实的先后就是通道顺序。缓冲交接面本 delta 未动;Side Chat 旧消费者由 workbar 兼容垫保留,WorkHub 直连。live-content-seed 删 generation 校验的合理性是推理验证(有序通道 + attempt 计数防陈旧),我未跑测试——验证边界如实:按本轮约束只读增量追缝,hosted test 绿,作者的 observer/preload 测试我读了没跑。本 approve 只覆盖本范围,不替其他线背书,不 merge。
) Thirty-one upstream commits. The one that reaches the new renderer is apache#5170, which gives the Renderer the transcript window: Main keeps a tail cache and answers page requests pass-through, `loadBefore` / `loadAfter` return a page, `loadAround` / `loadLatest` a reset, `acknowledgeTail` is new, and a batch carries `extends` / `coversFrom` / `navigation` instead of `evictedDurableSequences` / `completedOverlayMessageIds`. Also in: apache#5217's observation contract (`subscribeEvents` loses `onSeeded`; readiness follows seed consumption as the `ready` phase, and the execution projection it offers is not consumed here yet), the memory work across composer and stream (apache#5153), interactions cleared per Turn on abort/complete (apache#4562), Session bundles and external agents in main/preload (apache#5197, apache#5164), Code Mode (apache#3615, apache#5219), and the scheduled-task snooze fix (apache#5226). Resolution per the sync policy: conflicts under the old renderer's trees, packages/ui's deleted components, their stories, e2e specs and the main tests that import them stay deleted; upstream's new files in those trees are dropped (`application/contracts/settings-presentation`, `features/external-agent-settings`, `features/session-bundle`, `workhub/ui/return-button`, `model-wheel-picker`, the prompt-rail and live-turn-buffer tests, `workhub-return-rail.spec.ts`). The renderer side of apache#5217 (one live Turn per Session → a buffer keyed by Turn, `liveTurn` → `liveTurns`, `phase` gone) stays out: `packages/ui` `live-turn-projection.ts`, `transcript-projection.ts` and their tests keep ours and `live-turn-buffer.ts` is dropped; `session-event-handlers.ts` keeps ours plus upstream's display-frame scheduler. `packages/ui` `conversation-copy.ts` keeps `transcriptGap` (our gap rows use it), `transcript-row-projection.ts` is restored, `use-pending-selection.ts` goes. Astryx stays out of package.json and the lockfile; `@ai-sdk/provider-utils` moves to 5.0.40 and the `@ai-sdk/code-mode` override lands. Re-implemented for the new contract: - `lib/ported/desktop-transcript-range-store.ts` and `transcript-reading-position.ts` are re-ported from upstream head (the previous copies were format-only ports of the old versions); `TranscriptReadSupersededError` lives in the latter, and `display-frame-scheduler.ts` joins `lib/ported`. - `store/active-session-store.ts`: the window is the store's — the display follows a store subscription rather than `accept`'s return; the paging gate and `loadTranscriptHistory` are gone (the controller refuses a read against an edge it already read), `loadHistory` keeps only the gap-row indicator; `prefetchHistory` and `retainWindow` serve `useChatScroll`'s geometry-driven filling and trimming; `setReadingAnchor` only moves the bookmark; the bookmark re-anchors after a replica generation change, by sequence within a Host epoch and by Turn through the landmark index across one; a read superseded by an epoch change is not an error. - `SessionView` passes `onPrefetchHistory` / `onRetainWindow`; the gap rows and the return-to-latest button keep their explicit commands. - `bridge/sessions.ts` drops `onSeeded`. - Main tests for the range store, navigation race, overlay settlement and the two new probes are upstream's with paths under `lib/ported`; the reading-position test keeps upstream's pure-module cases (send pinning, overlay-only bookmark, superseded read) — the shell-shaped cases live with the store's tests. - `settings-sections.ts` and the copy files name core's new `external-agents` section id as a deferred page. - Ported apache#5226: an edit that leaves the schedule fields alone omits `schedule` from its patch, so the Host keeps a snoozed fire (`scheduled-task-form-payload.ts`, `ScheduleFormDialog.tsx`, a test in `scheduled-module.test.tsx`). Also in this tree, found while verifying the sync and not caused by it: a live Turn's finished steps vanished after switching to another task and back, leaving only "Working on it…". The Host re-seeds only what is still incomplete (the streaming text, pending interactions) and Main's transcript overlay is bootstrapped once per replica, so the steps that finished while the Session was on screen existed only in the renderer's live projection — which the store wiped on every selection and reseed. The projection now survives the switch (a reseed drops only the incomplete text and thinking it replays; a Turn that ended meanwhile is retired by the transcript it left behind). `test:streaming-switch` drives the real app through it with a new fake-backend scenario that settles a text step and a tool call, then holds the Turn open. The compatible-change declaration `base64-length-allocation.json` is re-pinned from 143 to the epoch this branch carries (147, upstream's own): upstream left it at the epoch of its commit and its per-commit hook never re-judged it, while our merge stages it next to the epoch bump. Its reason (Base64 byte counting in `artifact.ts` / `session-transcript.ts` without observable change) still holds against the protocol as merged. Gates: build:test + build:renderer, typecheck, biome lint and format, locale hygiene, ASF headers, renderer architecture ledger (rewritten with `--write`; the new range store's `window` local reads as environment capabilities to the checker), e2e budget, third-party notices, knip (same findings as before the merge), desktop dist tests (1599), renderer state (282), Electron smoke (44 checks, no renderer errors), core-dialogue smoke, streaming-switch smoke. `packages/storage` `workspace-identity` (git worktree ENOTEMPTY) is a parallel-run flake that passes in isolation, as is `packages/eval` `lifecycle-boundaries` (relay cancellation timing); `packages/runtime` `model-adapter-onerror` fails on this machine before and after the merge (asynchronous activity after the test ended; the file is unchanged this round). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…f0a5d) Eight upstream commits. One reaches the new renderer's contracts: apache#4878 returns expected failures as codes across IPC — the five Session setters (`setPermissionMode`, `setCollaborationMode`, `setOrchestrationMode`, `setModelConfiguration`, `setThinkingLevel`) answer `DesktopSessionUpdateResult` instead of throwing, `abandonPlanProposal` answers `PlanControlIpcResult` like its siblings, `send` / `submitMessage` gain an `attachment_blocked` refusal, and the attachment guard throws a typed `AttachmentIngestBlockedError` in place of the `attachment_ingest:<code>` message token. Also in: apache#5216 makes HTML artifacts directly openable (`app.showArtifactInFolder` beside `openArtifactPath`, which now hands an HTML artifact to the default app; `isArtifactUserVisible` admits HTML tool results), apache#5198 unifies WorkHub conversation identity and Host-owned choices (an Astryx `ChoicePanel` in packages/ui, `keyboardHint` copy, a `preserveFocus` scroll target), apache#5249's skill picker fix (`selectedSkillIds` in `chat-input-behavior`), apache#4815 admits structured-only Messages (`hasMeaningfulMessageContent` in core), apache#4862's ACP live session lifecycle in the CLI, apache#5204's workbar tab scrollbar css and apache#5180's wider locale hygiene gate. Resolution per the sync policy: conflicts under the old renderer's trees, packages/ui's deleted components, stories, e2e specs and the main tests that import them stay deleted, and upstream's new files there are dropped (`features/workhub/model/workspace-name.ts`, packages/ui's `choice-panel.tsx` and its `index.ts` export, the `styles/base.css` / `workhub.css` / `maka-tokens.css` edits, `expected-error-presentation.test.ts` and the WorkHub main tests). git's rename pairing had put upstream's `features/session-settings/ports.ts`, `features/workhub/testing.ts` and `platform/desktop/create-session-settings-services.ts` into `bridge/e2e-fixture.ts`, `components/ui/skeleton.tsx` and `lib/ported/display-frame-scheduler.ts`; all three keep ours. The renderer architecture ledger keeps ours, rewritten with `--write`. The e2e budget and `transcript-scroll-cost.spec.ts` keep ours; upstream's new `expected-failure-feedback.spec.ts` is trimmed to its second case (the IPC round trip of the setting and Plan codes), the first needing the WorkHub surface this build does not ship. Re-implemented for the new contracts: - `bridge/sessions.ts` unwraps every update result and rethrows a refusal as `ExpectedOperationError` (new `bridge/expected-operation-error.ts`, ported from upstream's `operation-diagnostics.ts`), so the turn actions store and every caller keep awaiting a summary. `localizedShellErrorMessage` renders the code through the new `updateFailures` copy on every surface and `AttachmentIngestBlockedError` by its `code`; `sessionSettingFailureCopy` is upstream's. - `ChatInput` routes an `attachment_blocked` refusal through upstream's `showSubmissionFeedback` and keeps the draft with the ingest reason; `showSkillInvocationFeedback` stays exported for the partial-success toast. - `FilesTab` reveals through `showArtifactInFolder`; an HTML row shows "View in Maka", its menu offers "Open in Default App" first, and its preview's external action opens rather than reveals. `artifact-copy` gains `viewInMaka` / `openInDefaultApp` in three locales. - `TipTapEditor` hides Skills already in the draft from the picker (apache#5249); the chips are atoms here, so the set is read from the document rather than from the `/skill:x` text upstream scans. - `composer-state.test.ts` asserts the typed preflight error. packages/ui: `use-chat-scroll.ts` merged cleanly (`preserveFocus`) on top of our `holdTurn` extensions; `conversation-copy.ts` and `chat-input-behavior.ts` take upstream's additions. apache#5217's live-turn buffer stays out as before. The compatible-change declaration is unchanged this round. The release checklist's baseline note records that the eight commits add no unlisted renderer surface. Gates: build:test + build:renderer, typecheck, biome lint and format, locale hygiene (the widened apache#5180 gate), ASF headers, renderer architecture ledger (rewritten with `--write`), e2e budget, third-party notices, knip (39 unused files, unchanged from the twelfth sync), workspace dist tests (desktop 3484 of 3498 with 14 skipped, every other workspace green), Electron smoke (44 checks, no renderer errors), core-dialogue smoke, streaming-switch smoke, and the trimmed `expected-failure-feedback` e2e case against the real preload. `packages/runtime` `model-adapter-onerror` fails on this machine before and after, as in the eleventh and twelfth syncs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
Starting another conversation turn could leave the working indicator attached to the previous answer. Desktop inferred execution from content and session-list refreshes instead of projecting the Host root Turn.
Use the Host execution snapshot for activity in the conversation, Side Chat, and WorkHub. Keep content buffered by Turn until durable transcript handoff. Observation loss hides running presentation while retaining content and the exact Stop target; it does not fabricate a Runtime terminal event. Ordinary sends use
next_turnintent so Host decides whether to start or queue them.Initialization and recovery now deliver execution and content together through the same observation event stream as live updates. Renderer becomes ready only after consuming that seed. The observe reply acknowledges registration and carries no content. Remove response replay, the independent readiness IPC channel/callback, and stale-response generation guards. Seed delivery is addressed to registered observer IDs, so a new subscriber in an existing window gets its complete snapshot without replaying it to other subscribers. Per-observer delivery bookkeeping prevents duplicate initial seeding during recovery.
Also close the migration gaps: reconcile the whole Turn buffer, bind compaction Stop and its pending request to the Host Turn, invalidate observation when recovery starts, suppress retained tool/compaction activity when unavailable, and remove unused single-Turn output, confirmation, phase, and terminal-effect paths.
Verification
AI use
OpenAI Codex implemented and tested the change. Claude Opus and Gemini provided architecture consultations; Raft reviewers independently reviewed earlier revisions. AI-authored commits carry Generated-by trailers.
Checklist
Affected checks passed; repository-wide formatting remains unverified.
Does this PR entail a change in behavior?