Skip to content

fix(desktop): recover evicted Runtime Host session streams - #2630

Merged
M4n5ter merged 4 commits into
mainfrom
fix/desktop-session-subscription-recovery
Aug 10, 2026
Merged

fix(desktop): recover evicted Runtime Host session streams#2630
M4n5ter merged 4 commits into
mainfrom
fix/desktop-session-subscription-recovery

Conversation

@M4n5ter

@M4n5terM4n5ter commented Aug 10, 2026

Copy link
Copy Markdown
Member
English

Summary

Recover Desktop session streams when a bounded Runtime Host subscription is evicted while a turn is active.

Dense Agent Graph activity can overflow a client subscription queue within one scheduling window. Runtime Host correctly closes that subscription as a slow consumer, but Desktop previously stopped observing the session while the renderer still expected live updates. The stream then appeared frozen until the user changed sessions and returned.

Desktop now gives each observed Session one replaceable subscription owner. The owner validates and commits a replacement subscription atomically, restores the canonical transcript prefix, and continues offset-bearing deltas without a renderer resubscribe. The observer remains responsible only for Desktop projection and renderer targets.

After in-place recovery or Runtime Host candidate replacement, Desktop reconciles terminal and successor turns, Goal state, exact pending interactions, and Task, Deep Research, Plan, Graph, Shell Run, and PTY projections. A mounted terminal reacquires its controller snapshot and continues receiving live PTY data.

Copied Desktop diagnostic reports now include Session, Turn, event, stable reason/code, recoverability, and redacted internal details. Main-process logs also record subscription recovery attempts and outcomes.

Verification

  • npm test --workspace @maka/desktop — 1,157 tests passed
  • npm run typecheck --workspace @maka/desktop
  • npm run build --workspace @maka/desktop
  • Biome and git diff --check
  • Recovery regressions cover Goal invalidation, interaction clearing, Graph/Shell resync, PTY controller reacquisition, snapshot hydration, and subsequent live PTY data
  • macOS Desktop smoke test with persisted Runtime Host state: the existing Graph transcript rendered and new thinking/tool updates continued streaming
  • Windows Desktop was not available locally; the original report and follow-up verification target Windows

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — Desktop automatically recovers bounded Session subscriptions and reconciles dependent live state
  • No
中文

概要

当有界 Runtime Host 会话订阅在活跃 turn 期间被淘汰时,自动恢复 Desktop 会话流。

密集的 Agent Graph 活动可能在一个调度窗口内产生超过客户端订阅队列容量的帧。Runtime Host 会按设计以 slow consumer 原因关闭该订阅,但此前 Desktop 会停止观察该会话,而 renderer 仍在等待实时更新。结果是界面流式输出看似冻结,只有切换到其他会话再返回后才恢复。

Desktop 现在为每个被观察的 Session 设置一个可替换的订阅 owner。该 owner 原子地验证并提交替代订阅,恢复权威 transcript 前缀,并在无需 renderer 重新订阅的情况下继续接收带 offset 的 delta。observer 只负责 Desktop 投影与 renderer target。

无论是原订阅就地恢复,还是 Runtime Host candidate 替换,Desktop 都会协调已结束 turn、successor turn、Goal 状态、精确的待处理交互,以及 Task、Deep Research、Plan、Graph、Shell Run 和 PTY 投影。已挂载的终端会重新获取 controller snapshot,并继续接收实时 PTY 数据。

复制出的 Desktop 诊断报告现在还会包含 Session、Turn、事件标识、稳定的 reason/code、是否可恢复以及经过脱敏的内部详情。主进程日志也会记录订阅恢复的开始和结果。

验证

  • npm test --workspace @maka/desktop — 1,157 项测试通过
  • npm run typecheck --workspace @maka/desktop
  • npm run build --workspace @maka/desktop
  • Biome 与 git diff --check
  • 恢复回归覆盖 Goal invalidation、交互清空、Graph/Shell resync、PTY controller 重新获取、snapshot 恢复及后续实时 PTY 数据
  • macOS Desktop 持久化 Runtime Host 状态冒烟测试:既有 Graph transcript 正常渲染,新的 thinking/tool 更新可持续流式显示
  • 本地没有 Windows Desktop 环境;原始报告与后续验证目标来自 Windows

检查清单

  • 测试覆盖本次行为,且在没有修复时会失败
  • lint、format、typecheck 与受影响测试均在本地通过

此 PR 是否包含行为变化?

  • 是 — Desktop 会自动恢复有界会话订阅,并协调依赖的实时状态

@M4n5ter
M4n5terforce-pushed the fix/desktop-session-subscription-recovery branch from cb522dd to ef90ff3CompareAugust 10, 2026 08:49
@M4n5ter
M4n5terforce-pushed the fix/desktop-session-subscription-recovery branch from ef90ff3 to 09ca667CompareAugust 10, 2026 09:36
@M4n5ter
M4n5ter marked this pull request as ready for review August 10, 2026 09:38
@M4n5ter
M4n5ter merged commit dc479d7 into mainAug 10, 2026
10 checks passed
@M4n5ter
M4n5ter deleted the fix/desktop-session-subscription-recovery branch August 10, 2026 09:41
me2seeks added a commit to me2seeks/maka-agent that referenced this pull request Aug 18, 2026
…ehind
A TUI turn event consumer that fell more than MAX_PENDING_EVENTS_PER_TURN
behind had its stream failed permanently while the Host turn kept running;
the channel kept draining frames into the dead queue and nothing recovered
until the terminal transcript refresh.
Shed offset-bearing deltas (healed by the next canonical resync or text
completion) and evict the oldest sheddable delta to make room for other
events so terminal records always land, and notify the channel once per lag
episode. The channel retires the healthy-but-lagged subscription through the
existing recovery path — the same resubscribe a Host slow-consumer eviction
triggers, and the TUI equivalent of the Desktop subscription owner (apache#2630).
Fixesapache#3180
Generated-by: Maka
me2seeks added a commit to me2seeks/maka-agent that referenced this pull request Aug 18, 2026
…ehind
A TUI turn event consumer that fell more than MAX_PENDING_EVENTS_PER_TURN
behind had its stream failed permanently while the Host turn kept running;
the channel kept draining frames into the dead queue and nothing recovered
until the terminal transcript refresh.
Shed offset-bearing deltas (healed by the next canonical resync or text
completion) and evict the oldest sheddable delta to make room for other
events so terminal records always land, and notify the channel once per lag
episode. The channel retires the healthy-but-lagged subscription through the
existing recovery path — the same resubscribe a Host slow-consumer eviction
triggers, and the TUI equivalent of the Desktop subscription owner (apache#2630).
Fixesapache#3180
Generated-by: Maka
Astro-Han pushed a commit that referenced this pull request Aug 20, 2026
…n loops (#3181)
* fix(cli): resubscribe instead of failing when a turn consumer falls behind
A TUI turn event consumer that fell more than MAX_PENDING_EVENTS_PER_TURN
behind had its stream failed permanently while the Host turn kept running;
the channel kept draining frames into the dead queue and nothing recovered
until the terminal transcript refresh.
Shed offset-bearing deltas (healed by the next canonical resync or text
completion) and evict the oldest sheddable delta to make room for other
events so terminal records always land, and notify the channel once per lag
episode. The channel retires the healthy-but-lagged subscription through the
existing recovery path — the same resubscribe a Host slow-consumer eviction
triggers, and the TUI equivalent of the Desktop subscription owner (#2630).
Fixes#3180
Generated-by: Maka
* test(cli): cover turn consumer lag recovery
Flood an unconsumed turn stream past its bound: the channel resubscribes,
the stream never rejects, live deltas continue after recovery, and terminal
events still land while deltas are shed.
Generated-by: Maka
* fix(cli): guarantee terminal admission and compact lagged delta backlog on resync
Address qodo-code-review findings on #3181:
- A full queue with no sheddable delta silently dropped an incoming
complete/error/abort, letting the consumer reach end-of-stream without a
terminal outcome. Terminal outcomes now evict the oldest event in that
corner, so they always land.
- After lag recovery, a still-full queue kept shedding the fresh
post-resync stream behind stale deltas the canonical replacement had
already superseded. Lagging queues now drop their unseen sheddable
backlog when the canonical replacement lands.
Generated-by: Maka
* test(cli): pin terminal admission and resync backlog compaction
Both new tests fail against the pre-review channel: the resync marker delta
is shed behind the uncompacted backlog, and the terminal outcome is dropped
from an all-tool-event backlog. Deterministic ordering: wait for the
canonical resync before producing the terminal frame, so the backlog is
still full when it is emitted.
Generated-by: Maka
* fix(cli): re-arm lag detection with hysteresis after the consumer drains
CodeRabbit review on #3181: after a lag recovery over a non-delta backlog,
the latch stayed on until the queue fully emptied, so fresh output shed
while the consumer was still behind could never schedule another canonical
recovery. Re-arm once the backlog drains to half the bound: a consumer
making progress gets later episodes recovered, while a wedged consumer
never drains and cannot loop resubscribes.
Generated-by: Maka
* test(cli): cover repeated lag recovery after consumer progress
Sends fresh output after a non-delta-backlog recovery, then lags the
consumer a second time and expects a third subscription. Fails without the
hysteresis re-arm: the latch never clears while events remain queued.
Generated-by: Maka
* test(cli): keep the second subscription's frame stream contiguous
CodeRabbit review on #3181: the repeated-recovery test sent sequences
1, then 100..199, then a flood restarting at 1 with the first
subscription's id — a stream the real ClientSessionSubscription would
reject as a sequence gap, masked by the fake. Thread the subscription id
and a starting sequence through floodToolStream so every fake stream stays
valid.
Generated-by: Maka
* fix(cli): shed lagged tool output deltas so tool results land
Astro-Han review on #3181 (P2): during a tool-output flood the queue
filled with non-sheddable tool_output_delta events, so an incoming
tool_result had nothing to evict and was silently dropped, leaving the
live tool card stuck at "running" until the durable transcript healed.
tool_output_delta is sheddable by design: the protocol documents its
chunks as transient UI updates with a monotonic per-tool seq that
renderers de-dupe and order by, and the terminal tool_result plus the
durable transcript remain the authoritative output. Shedding them under
lag matches the text_delta story; a shed range leaves a display gap,
never corruption.
The remaining boundary is documented at the drop branch: a non-delta,
non-terminal event behind a backlog with nothing sheddable (e.g. an
all-control backlog) is still dropped, and the durable transcript heals
the terminal state.
Generated-by: Maka
* test(cli): pin the lag hysteresis watermark boundary
Astro-Han review on #3181 (P3s): the re-arm boundary was only
indirectly covered. The new test drains a full non-delta backlog to one
event above the watermark (513 pending) and asserts a fresh overflow
does not resubscribe, then drains to the watermark (512 pending) and
asserts the next overflow is treated as a new lag episode.
Also pin two reviewed behaviors in comments: the retiring-subscription
guard swallowing a genuine error racing the deliberate close (the
replacement pump re-surfaces real failures via #fail), and the per-queue
lag escalating to a session-wide recovery (benign superset: the resync
heals every turn, and the latch plus hysteresis prevent resubscribe
loops).
Generated-by: Maka
* fix(runtime-host): coalesce queued assistant deltas instead of evicting
Dogfooding this branch surfaced the loop the resubscribe fix could spin:
an active turn's thinking/text delta flood outpaces the one-awaited-send
flush, the 32-frame subscriber budget overflowed, and the Host evicted the
subscription as slow_consumer within seconds of every resubscribe — while
the recovering channel had a permanently fatal path (clean iterator end
mid-catch-up) and a silently freezing one.
The coordinator now folds a queued assistant delta into its queued tail
when it continues the same stream contiguously: projectors apply deltas by
absolute startOffset, so a merged frame carries byte-identical content, and
the absorbed frame never spends a sequence. Eviction stays the backstop for
genuinely undrainable backlogs (covered by alternating-stream tests).
The channel treats a live stream that ends without subscription.closed as
connection_closed, routing it through resync recovery instead of failing
the session.
Co-Authored-By: Maka <noreply@maka.dev>
Generated-by: Maka
* fix(runtime-host): bound coalesced session deltas
Generated-by: Maka
* fix(cli): retain tool results under lag
Generated-by: Maka
* fix(cli): make session recovery a bounded sequence cut
Generated-by: Maka
* fix(cli): preserve bounded recovery outcomes
* test(cli): exercise post-resync backlog admission
* style(cli): format lag admission coverage
* fix(cli): close remaining lag recovery boundaries
Start recovery stability only after a post-hydration live frame, recover buffered slow-consumer closure during initial hydration, and guarantee assistant completion admission over saturated control backlogs.
Generated-by: Maka
---------
Co-authored-by: Maka <noreply@maka.dev>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@M4n5ter