mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-08 14:52:42 +08:00
fix(chat): make message queuing reliable across sessions and turn boundaries
Queued messages had four related defects: - A queued message flashed and then vanished at flush time: concurrent transcript refreshes (the `complete` handler racing the watcher-triggered update) could resolve out of order, letting a stale response overwrite newer server messages after the optimistic row was pruned. Session slots now carry a monotonic fetch ticket and discard stale fetch/refresh/ fetchMore responses. - Switching sessions flushed the previous session's queued draft into the newly viewed one (sending it with the wrong provider's settings, e.g. a Claude model into a Codex session). The composer flush is now scoped to its session, and a draft restored into an idle session sends after a short grace period so a live-run ack can cancel it. - The thinking banner never appeared for a queued turn: the chat handler's session-keyed completeRun safety net could fire after a queued message had already started the session's next run, emitting a spurious `complete` that killed it. The safety net is now scoped to its own run via completeRunIfCurrent (with a regression test). - Queued messages only sent while their session was being viewed. Drafts now persist their send options (model, effort, permissions) at queue time, and a new app-level useQueuedMessageAutoSend hook dispatches a non-viewed session's queued message as soon as its run completes, using the storage key as the claim ticket to prevent double sends.
This commit is contained in:
@@ -318,6 +318,22 @@ export const chatRunRegistry = {
|
||||
run.writer.sendComplete(opts);
|
||||
},
|
||||
|
||||
/**
|
||||
* Safety-net variant of `completeRun` scoped to one specific run: a no-op
|
||||
* unless `run` is still the session's current, running run. A runtime
|
||||
* promise can resolve after its own `complete` already streamed AND a new
|
||||
* run has replaced it in the registry (a queued message sends within
|
||||
* milliseconds of the previous turn ending) — the session-keyed
|
||||
* `completeRun` would terminate that newer run.
|
||||
*/
|
||||
completeRunIfCurrent(run: ChatRun, opts: { exitCode: number; aborted?: boolean }): void {
|
||||
if (runs.get(run.appSessionId) !== run || run.status !== 'running') {
|
||||
return;
|
||||
}
|
||||
|
||||
run.writer.sendComplete(opts);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test-only escape hatch: clears every tracked run.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user