🐛 fix(client): 授权与提问提醒不再被运行状态丢掉

This commit is contained in:
pyh
2026-10-04 19:24:02 +08:00
parent 248619ffc3
commit 074cdccdb9
3 changed files with 73 additions and 36 deletions
+18 -6
View File
@@ -7,7 +7,7 @@
import { SETTLE_MS, THROTTLE_MS } from '../constants.js'
import { windowIsAway } from '../platform.js'
import { isOnScreen, stillWorth } from './session.js'
import { isOnScreen, stillOwed } from './session.js'
/**
* Build the delivery path.
@@ -67,29 +67,33 @@ export function createDelivery({ store, copy, live, toasts, system, replay }) {
* never actually receives.
* @param candidate - the alert to deliver.
* @param ignoreThrottle - deliver immediately, bypassing the flood guard.
* @returns `false` when the flood guard suppressed this alert, `true` when the
* delivery path itself ran (including a deliberate skip for the conversation
* on screen).
*/
const deliver = (candidate, ignoreThrottle) => {
if (ignoreThrottle !== true) {
const now = Date.now()
if (now - lastDelivery < THROTTLE_MS) return
if (now - lastDelivery < THROTTLE_MS) return false
lastDelivery = now
}
if (!windowIsAway()) {
if (isOnScreen(candidate.sessionId, live.list)) return
if (isOnScreen(candidate.sessionId, live.list)) return true
toasts.showToast(candidate)
recordDelivery('popup', candidate)
return
return true
}
const result = system.notifySystem(candidate, candidate.test === true)
if (result.outcome === 'raised') {
recordDelivery('system', candidate, result)
if (candidate.test !== true) replay.holdForReplay(candidate, result)
return
return true
}
// A test alert still has to reach the user, and so does a real one when
// the system channel refuses: the popup is the channel that remains.
toasts.showToast(candidate)
recordDelivery(`system-refused-${result.outcome}`, candidate)
return true
}
/**
@@ -98,11 +102,19 @@ export function createDelivery({ store, copy, live, toasts, system, replay }) {
* One candidate per tick keeps the throttle meaningful, and anything still
* queued arms its own follow-up tick — a burst used to leave every candidate
* after the first stranded in the queue with no timer to flush it.
*
* A pending interaction the flood guard holds back is put back in the queue
* instead of being dropped: only the user can clear it, so it stays owed, and
* the next tick retries it. Flood control still drops the completions it was
* written for.
*/
const flushSettle = () => {
settleTimer = 0
const candidate = pending.shift()
if (candidate !== undefined && stillWorth(candidate, live)) deliver(candidate, false)
if (candidate !== undefined && stillOwed(candidate, live)) {
const delivered = deliver(candidate, false)
if (delivered === false && candidate.pendingKind !== '') pending.push(candidate)
}
if (pending.length > 0) settleTimer = setTimeout(() => flushSettle(), SETTLE_MS)
}