fix(webchat): stabilize live transcript run state#85956
Conversation
|
@clawsweeper review Please review this as the canonical maintainer PR for #83528 and #82611. Focus on the early Codex prompt mirror idempotency, exact selected-session hidden-run delivery, and Control UI selected-session subscription/run-id adoption boundaries. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Codex review: needs maintainer review before merge. Reviewed May 24, 2026, 11:49 PM ET / 03:49 UTC. Summary PR surface: Source +289, Tests +404, Docs +1, Other -1. Total +693 across 44 files. Reproducibility: yes. source inspection gives a high-confidence reproduction basis: current main requires exact UI session-key matches for passive run handling and does not subscribe the selected Control UI session message stream, while Codex transcript mirroring only emits file-level updates after append. I did not run tests because this review is read-only. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge Security Review detailsBest possible solution: Land this focused fix after maintainer review accepts the targeted regression proof and any desired live observer proof, then close the linked user reports through the merged PR. Do we have a high-confidence way to reproduce the issue? Yes, source inspection gives a high-confidence reproduction basis: current main requires exact UI session-key matches for passive run handling and does not subscribe the selected Control UI session message stream, while Codex transcript mirroring only emits file-level updates after append. I did not run tests because this review is read-only. Is this the best way to solve the issue? Yes, this is a maintainable repair shape: it reuses the existing transcript idempotency and sessions.messages.subscribe contracts, adds alias-aware comparisons at the UI boundary, and preserves suppressNextUserMessagePersistence for already-persisted prompts. Codex review notes: model gpt-5.5, reasoning high; reviewed against 9db04a27eb20. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +289, Tests +404, Docs +1, Other -1. Total +693 across 44 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
There was a problem hiding this comment.
Pull request overview
Stabilizes WebChat/Control UI live transcript run state for external-channel sessions by making inbound prompts visible earlier, tightening live event routing for hidden runs, and improving client-side run/stream handling.
Changes:
- Codex app-server now mirrors the user prompt into the transcript at turn start using the existing
${turnId}:promptidentity for idempotent dedupe later. - Gateway routes live chat/agent updates for hidden (non-Control-UI-visible) runs only to selected-session message subscribers instead of broadcasting globally.
- Control UI subscribes/re-subscribes to selected-session message streams on connect/reconnect and session switch, adopts observed runIds for passive observation, and deduplicates accumulated stream snapshots around tool cards.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/ui/controllers/sessions.ts | Adds selected-session message stream subscribe/unsubscribe syncing state. |
| ui/src/ui/controllers/sessions.test.ts | Adds tests for selected-session message stream subscription behavior. |
| ui/src/ui/controllers/chat.ts | Adopts observed runId for selected-session deltas when no local run is active. |
| ui/src/ui/controllers/chat.test.ts | Adds coverage for runId adoption from externally-originated deltas. |
| ui/src/ui/chat/build-chat-items.ts | Deduplicates accumulated stream snapshots around tool card boundaries. |
| ui/src/ui/chat/build-chat-items.test.ts | Tests stream snapshot deduplication across tool cards. |
| ui/src/ui/app.ts | Adds app state field for selected-session message subscription key. |
| ui/src/ui/app-view-state.ts | Extends view-state typing for selected-session message subscription key. |
| ui/src/ui/app-settings.refresh-active-tab.node.test.ts | Updates mocks for new sessions controller export. |
| ui/src/ui/app-render.helpers.ts | Subscribes selected-session message stream on session switch. |
| ui/src/ui/app-render.helpers.node.test.ts | Asserts session switch triggers selected-session message subscription sync. |
| ui/src/ui/app-gateway.ts | Syncs selected-session message subscription on connect/reconnect (forced). |
| ui/src/ui/app-gateway.sessions.node.test.ts | Updates sessions controller mocks for new export. |
| ui/src/ui/app-gateway-chat-load.node.test.ts | Updates sessions controller mocks for new export. |
| src/gateway/server-runtime-subscriptions.ts | Plumbs sessionMessageSubscribers into agent event handler wiring. |
| src/gateway/server-chat.ts | Adds targeted delivery for hidden runs via session message subscribers; refactors send helpers. |
| src/gateway/server-chat.agent-events.test.ts | Verifies hidden live chat is delivered only to exact session message subscribers. |
| extensions/codex/src/app-server/run-attempt.ts | Mirrors Codex prompt into transcript at turn start (best-effort). |
| extensions/codex/src/app-server/run-attempt.test.ts | Tests early prompt mirroring + idempotent dedupe with end-of-turn mirror. |
| CHANGELOG.md | Documents the WebChat/Control UI stability fix and related issues. |
|
ClawSweeper PR egg ✨ Hatched: 🌱 uncommon Tiny Signal Puff Hatch commandComment Hatchability rules:
Rarity: 🌱 uncommon. What is this egg doing here?
|
|
@clawsweeper re-review Follow-up commit
Verification on this head:
|
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review Boundary-only follow-up
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review Follow-up commit
Latest proof on head
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
e7d411c to
318eb0d
Compare
|
@copilot resolve the merge conflicts in this pull request |
Resolved. I merged the latest |
fc688af to
a3b75ec
Compare
|
@clawsweeper re-review Latest cleaned/rebased head: I rebased the PR stack onto current Proof on this head:
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
a3b75ec to
23f7c0f
Compare
|
Maintainer merge verification for
Local proof on the cleaned stack: Known proof gap accepted for merge: no fresh live Feishu/Telegram browser observer proof was run in this pass; the PR has focused local regressions plus current-head CI and sufficient proof labels. |
Summary
Fixes #83528 and #82611; refs #83949.
${turnId}:promptidentity so WebChat can see external-channel user messages before the turn finishes, while preservingsuppressNextUserMessagePersistencefor already-persisted inbound prompts.session.messagereloads alias-aware.messageSeqvalues for multi-message Codex mirror batches so selected-session subscribers receive per-message transcript positions instead of post-batch duplicates.Real behavior proof
session.messagehandling across the default main alias/canonical key pair, reconnect-safe selected-session subscription state, stable mirrored transcript message sequences, suppression-safe prompt mirroring, and no duplicated accumulated stream text around tool cards.meow/webchat-transcript-run-state-truth, latest head23f7c0f47037.OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs extensions/codex/src/app-server/run-attempt.test.ts -- -t "does not mirror the Codex prompt early when user message persistence is suppressed"OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs extensions/codex/src/app-server/run-attempt.test.ts extensions/codex/src/app-server/transcript-mirror.test.tsOPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs src/gateway/server-chat.agent-events.test.ts src/gateway/session-message-events.test.tsOPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs ui/src/ui/controllers/chat.test.ts ui/src/ui/controllers/sessions.test.ts ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/app-gateway.sessions.node.test.ts ui/src/ui/app-render.helpers.node.test.ts ui/src/ui/app-gateway-chat-load.node.test.ts ui/src/ui/app-settings.refresh-active-tab.node.test.tsnode scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfonode --import tsx scripts/check-no-extension-test-core-imports.tsgit diff --check origin/main..HEADcheck-no-extension-test-core-importschecked 1920 extension files and 1 plugin helper.git diff --check origin/main..HEADpassed on latest head.messageSeq[1, 2], selected-session sequence delivery, alias/canonical subscription de-dupe, stale subscription completion cleanup, main-aliassession.messagereloads, and passive main-alias run-id adoption.agent:main:maintranscript events reload selectedmain; passive chat state adopts observed run ids; and accumulated stream snapshots render only their new suffix after tool cards.pnpm check:changedthrough Crabbox was attempted before this follow-up but blocked: Blacksmith Testbox is not configured for.github/workflows/crabbox-hydrate.yml, and default AWS Crabbox could not refresh credentials because no EC2 IMDS role/credentials were available on this machine.Verification
OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs extensions/codex/src/app-server/run-attempt.test.ts -- -t "does not mirror the Codex prompt early when user message persistence is suppressed"OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs extensions/codex/src/app-server/run-attempt.test.ts extensions/codex/src/app-server/transcript-mirror.test.tsOPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs src/gateway/server-chat.agent-events.test.ts src/gateway/session-message-events.test.tsOPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 node scripts/run-vitest.mjs ui/src/ui/controllers/chat.test.ts ui/src/ui/controllers/sessions.test.ts ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/app-gateway.sessions.node.test.ts ui/src/ui/app-render.helpers.node.test.ts ui/src/ui/app-gateway-chat-load.node.test.ts ui/src/ui/app-settings.refresh-active-tab.node.test.tsnode scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfonode --import tsx scripts/check-no-extension-test-core-imports.tsgit diff --check origin/main..HEAD