mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-19 05:54:13 +00:00
* fix(web-shell): prevent queued-prompt loss from drain race The auto-drain effect popped a queued prompt, called setQueuedPrompts, then submitted via setTimeout(0). Because the daemon flips streamingState asynchronously, the setState re-render could re-run the effect and pop a second prompt before the first registered as streaming — both submitted back-to-back and the first was lost. Arm an "awaiting turn start" gate synchronously at pop so the re-run is blocked until streamingState goes non-idle, released by a dedicated effect with a safety-net timer for a prompt that never streams (e.g. a queued slash command). Cleanup no longer cancels/re-queues the pending submit while the gate is armed. * feat(web-shell): friendlier Esc interruption + queued-prompt UX * refactor(web-shell): tidy Esc/queue code per review Behavior-preserving cleanups addressing review feedback on the Esc-interruption and queued-prompt changes: - Remove the now-dead queue.footer i18n key (EN + ZH) and the unreferenced .queuedHint CSS, orphaned when the Esc-clears-queue behavior was dropped. - Co-locate the queued-prompt styles in QueuedPromptDisplay.module.css instead of reaching into the parent App.module.css. - Make the Esc confirm-window constants the single source of truth: export them from escapeIntent.ts and drive the countdown-ring duration from one of them via a CSS custom property. - Nudge the queue-drain safety net with a dedicated tick counter instead of cloning queuedPrompts, so it no longer re-renders the composer for a no-op. - Drop a redundant !compact guard in StatusBar left over from flattening a ternary. - Document the pop/gate-arm ordering invariant in the drain effect.
38 lines
1.4 KiB
TypeScript
38 lines
1.4 KiB
TypeScript
// Pure gate for the queued-prompt auto-drain, extracted from App's drain effect
|
|
// so the "may I drain the next prompt right now?" conditions are a named,
|
|
// tested contract. This covers the boolean gate only — the effect still owns
|
|
// the timing (arming the turn-start gate, the setTimeout submit). The race that
|
|
// gate guards against is inherently effect-level and is verified separately.
|
|
|
|
export interface QueueDrainGate {
|
|
/** A drain is already in flight this tick. */
|
|
draining: boolean;
|
|
/** Waiting for the previously drained prompt's turn to start. */
|
|
awaitingTurnStart: boolean;
|
|
/** The daemon connection is live. */
|
|
connected: boolean;
|
|
/** A turn is in flight (streamingState !== 'idle'). */
|
|
streaming: boolean;
|
|
/** Some interaction (dialog, catch-up) is blocking input. */
|
|
interactionBlocked: boolean;
|
|
/** A tool approval is pending. */
|
|
pendingApproval: boolean;
|
|
/** Number of prompts currently queued. */
|
|
queueLength: number;
|
|
}
|
|
|
|
/**
|
|
* Whether the next queued prompt may be auto-drained into a new turn right now.
|
|
* Every condition must hold; any one being unmet holds the queue.
|
|
*/
|
|
export function canDrainQueue(gate: QueueDrainGate): boolean {
|
|
return (
|
|
!gate.draining &&
|
|
!gate.awaitingTurnStart &&
|
|
gate.connected &&
|
|
!gate.streaming &&
|
|
!gate.interactionBlocked &&
|
|
!gate.pendingApproval &&
|
|
gate.queueLength > 0
|
|
);
|
|
}
|