Skip to content

fix(desktop): bind Side Conversation events to Host admission - #3716

Open
Astro-Han wants to merge 25 commits into
apache:mainfrom
Astro-Han:fix/side-conversation-event-ownership
Open

fix(desktop): bind Side Conversation events to Host admission#3716
Astro-Han wants to merge 25 commits into
apache:mainfrom
Astro-Han:fix/side-conversation-event-ownership

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Give every Side Conversation send or steer one admission ticket; Runtime Host remains the sole authority that accepts or retracts it and names the exact owning Turn.
  • Keep an already owned Turn and a pending admission as two simultaneous Host facts. The active Turn continues streaming while the new ticket waits; a queued-ticket retraction clears only that ticket.
  • Bind direct starts from the Host command result. For queued current-Turn and successor-Turn admission, consume only the Host-owned message_admission fact; unrelated terminal, steering, and queue events never infer ownership.
  • Buffer only events whose owner is not known yet. Events for the already owned Turn continue through the normal live projection immediately.
  • Preserve unresolved admission across recoverable subscription failure and Host replacement. The complete catalog-preflight-plus-observer-registration operation uses the existing reconnectable Desktop IPC seam.
  • Scope admission projection, retry, unknown-outcome, and Stop fencing to canonically labelled Side Conversation Sessions. Ordinary Desktop/CLI streams keep their previous contract.

No durable message schema, settlement/recovery policy, CLI projection, or Desktop primary-conversation projection is changed.

Review follow-up

  • A queued steer Stop now waits for the Host retracted event instead of treating a successful Stop RPC as global idle. The still-running Turn remains owned and interruptible, and a second Stop can target it.
  • Stop fencing is owned by the current Stop promise, so a late failure from an older queued-ticket Stop cannot release a newer active-Turn Stop.
  • A queued ticket entering the active Turn now produces the same Host-owned message_admission fact as successor admission; observation reseeding replays that fact while the in-flight queue entry remains authoritative.
  • If observation misses the entire in-flight window, reseeding recovers the same exact ticket/Turn ownership from the existing durable steering UserMessage; no renderer recovery state or new protocol field is needed.
  • sessions:observe is registered as one reconnectable read operation, so Runtime Host replacement retries catalog preflight and observer registration together.
  • The React test seam now forwards to the latest Hook callbacks; the previous captured callback could leave steer tests exercising a stale turnInFlight=false closure.

Behavior evidence

The production-shaped React hook + Workbar seam covers:

  • old Turn settled, new send waiting for Host admission, then a late old terminal event;
  • the active Turn continuing to project text while a steer waits for admission;
  • successor events replaying only after the exact ticket is admitted;
  • queued-ticket Stop preserving active-Turn streaming and allowing a later active-Turn Stop;
  • an older Stop failure not releasing a newer Stop fence;
  • explicit queue retraction using the submitted ticket;
  • recoverable subscription failure and Host replacement preserving the original ticket;
  • busy retry identity, fork/copy cleanup, disposal, and observation-seed failure.

Protocol boundary

A full Host replacement can discard an old in-memory queued ticket before it reaches either an active Turn or a durable successor admission. Current Host facts can then prove neither admitted nor retracted; the existing submit contract intentionally reports outcome_unknown. This PR preserves that ticket instead of inventing cancellation. Tickets that reached an active Turn are recovered from the durable steering message. Fully settling the never-admitted queue-only case requires a prior Host protocol fact for the durable terminal disposition of a queued admission; it is outside this PR's storage/recovery scope.

Verification

  • Desktop typecheck: preload, main, renderer, and Storybook
  • 107 focused Desktop tests across Quote Companion, Runtime Host execution/observation/replacement, disposal, and Workbar adapter
  • 68 focused Runtime Host projector/protocol tests
  • 36 Runtime event-mapper tests
  • production renderer build and third-party notice check
  • ASF source-header check
  • git diff --check

Exact pushed head: e896f4648, rebased onto current origin/main. Exact-head CI is green, including Desktop e2e, Runtime Host, Browser/Storybook smoke, alignment, and installed CLI candidate validation. Earlier pre-rebase runs reached 64/66 Desktop e2e tests before the slash-command menu DOM-stability assertion failed; the rebase removed that failure and the full five-test Slash menu spec also passes locally.

AI use

Codex contributed the implementation, regression tests, verification, and adversarial/simplification review. No commit from Draft PR #3633 was cherry-picked or copied; its old branch/review was used only as failure evidence.

@Astro-Han
Astro-Han force-pushed the fix/side-conversation-event-ownership branch 2 times, most recently from 41aa14c to f51928a Compare August 24, 2026 19:38
@Astro-Han
Astro-Han marked this pull request as ready for review August 25, 2026 01:23

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this head and found blocking issues.

[P2] Queued steer Stop retracts the queued turn but drops UI ownership of the still-running turn

use-quote-companion.ts:312-320,723-758 with runtime-host-session-execution-ipc-main.ts:816-840. When a Side Conversation owns active A and steered S is still queued, Stop retracts only S without interrupting A. The renderer restores A but abandonAdmission() clears turnInFlight, so later A deltas render while the Composer appears non-streaming.

[P2] Ordinary Desktop observation now requires a catalog preflight that may not recover

runtime-host-session-execution-ipc-main.ts:192-206 with preload.ts:1722-1775. If Host reconnect interrupts getSession before registry registration, no observation is registered and live events are lost until remount. Previously sessions:observe registered directly.

Additional standards note: UI changes should include before/after visual evidence per CONTRIBUTING.md:85.

Checks on 364dd1a96 are test: success but mergeable: dirty — needs rebase.

简体中文存在两项状态机/观测阻断,需修复后再审。

@Astro-Han
Astro-Han force-pushed the fix/side-conversation-event-ownership branch from 364dd1a to b2c9946 Compare August 25, 2026 03:39
Derive admission observation from the Host-owned Session mode and keep ordinary Session streams unchanged. Remove the duplicate renderer send intent and tighten continuity decoding.

Generated-by: Codex
Keep Session event listeners bound to the current validated Host target epoch instead of filtering replacement events through the original host identity.

Generated-by: Codex
Reconcile buffered Host admission events when Stop outcome is unknown and bind successor/retraction fixtures to the actual submitted ticket.

Generated-by: Codex
Follow replacement target epochs within the owning profile while rejecting same-named Session channels from other Hosts.

Generated-by: Codex
Assert that replaying admission and text after an unknown Stop restores the real Host Turn streaming projection.

Generated-by: Codex
@Astro-Han
Astro-Han force-pushed the fix/side-conversation-event-ownership branch from 695ddfa to e896f46 Compare August 25, 2026 04:24
Sign up for free to 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