refactor(desktop): tighten account dedup, probe scheduling, and session cleanup

- Dedup AI platform accounts by platform ID rather than identity key, preferring higher health rank then newer verified_at
- Clean up session data and health records when duplicate or replaced accounts are removed
- Remove reconcileTrackedAccountRemoteState — no longer optimistically trust remote health state
- Spread initial probes over a random window (5s–3min) to avoid thundering-herd on startup
- Stop scheduling a next probe after an account is marked expired
- Update AI platforms stats strip to show authorized/pending instead of configured/authorized

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Xu Liang
2026-05-06 15:38:53 +08:00
parent ccffe87e28
commit a99d7a4971
3 changed files with 95 additions and 85 deletions
+4 -54
View File
@@ -19,6 +19,8 @@ import { getPersistedPartition, getSessionHandle } from './session-registry'
const PROBE_TICK_MS = 10_000
const STALE_AFTER_MS = 45 * 60_000
const INITIAL_PROBE_MIN_MS = 5_000
const INITIAL_PROBE_MAX_MS = 3 * 60_000
const REGULAR_PROBE_MIN_MS = 30 * 60_000
const REGULAR_PROBE_MAX_MS = STALE_AFTER_MS
const CONFIRM_BACKOFF_STEPS_MS = [30_000, 120_000] as const
@@ -103,7 +105,7 @@ function resolveKnownPartition(accountId: string): string | null {
}
function nextInitialProbeAt(now = Date.now()): number {
return now
return now + randomInt(INITIAL_PROBE_MIN_MS, INITIAL_PROBE_MAX_MS)
}
function nextRegularProbeAt(now = Date.now()): number {
@@ -311,7 +313,7 @@ function applyExpiredResult(
record.probeState = 'idle'
record.authReason = result.reason ?? 'login_redirect'
record.lastProbeAt = now
record.nextProbeAt = nextRegularProbeAt(now)
record.nextProbeAt = null
record.lastAuthFailureAt = now
record.suspectedExpiredAt = null
record.confirmFailureCount = 0
@@ -795,58 +797,6 @@ export function markTrackedAccountMissingPartition(
return snapshot
}
export function reconcileTrackedAccountRemoteState(
account: PublishAccountIdentity,
input: {
health?: DesktopAccountInfo['health'] | null
verifiedAt?: string | null
profile?: AccountHealthProfile | null
},
): AccountHealthSnapshot | null {
if (input.health !== 'live') {
return getAccountHealthSnapshot(account.id)
}
const remoteVerifiedAt = parseVerifiedAt(input.verifiedAt)
if (!remoteVerifiedAt) {
return getAccountHealthSnapshot(account.id)
}
const record = ensureRecord(account)
const newestLocalStateAt = Math.max(
record.lastVerifiedAt ?? 0,
record.lastAuthFailureAt ?? 0,
record.lastProbeAt ?? 0,
)
if (remoteVerifiedAt <= newestLocalStateAt) {
return toPublicSnapshot(record)
}
const previousSignature = snapshotSignature(record)
record.authRevision += 1
record.platform = account.platform
record.authState = 'active'
record.probeState = 'idle'
record.authReason = 'bind_success'
record.lastVerifiedAt = remoteVerifiedAt
record.lastProbeAt = remoteVerifiedAt
record.nextProbeAt = nextRegularProbeAt()
record.lastAuthFailureAt = null
record.profile = input.profile ?? record.profile
record.sessionFingerprint = input.profile?.subjectUid ?? record.sessionFingerprint
record.suspectedExpiredAt = null
record.confirmFailureCount = 0
const snapshot = commitRecord(record, previousSignature)
const queuedJob = probeJobs.get(account.id)
if (queuedJob?.status === 'queued') {
removeQueuedProbeJob(account.id)
queuedJob.resolve(snapshot)
}
return snapshot
}
export function getAccountHealthSnapshot(accountId: string): AccountHealthSnapshot | null {
ensureInitialized()
const record = records.get(accountId)