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
@@ -32,7 +32,6 @@ import {
markTrackedAccountMissingPartition,
markTrackedAccountBound,
probeTrackedAccount,
reconcileTrackedAccountRemoteState,
reportAccountFailure,
syncTrackedAccounts,
} from './account-health'
@@ -326,21 +325,67 @@ function isAccountOwnedByCurrentClient(account: DesktopAccountInfo): boolean {
}
function hasLocalAccountAuthorization(account: DesktopAccountInfo): boolean {
return isAccountOwnedByCurrentClient(account)
return isAccountOwnedByCurrentClient(account) && hasLocalAccountSession(account.id)
}
function accountRequiresLocalAuthorization(account: DesktopAccountInfo): boolean {
return Boolean(account.client_id)
}
function accountRequiresStrictProbe(account: DesktopAccountInfo): boolean {
return ['bilibili', 'juejin', 'smzdm'].includes(account.platform)
}
function shouldReportAccountPresence(account: DesktopAccountInfo): boolean {
return hasLocalAccountSession(account.id) && isAccountOwnedByCurrentClient(account)
}
function buildRuntimeAccountDedupeKey(account: DesktopAccountInfo): string {
if (isAIPlatformId(account.platform)) {
return `ai-platform:${account.platform}`
}
return buildAccountIdentityKey({
platform: account.platform,
platformUid: account.platform_uid,
displayName: account.display_name,
})
}
function accountHealthRank(health: DesktopAccountInfo['health']): number {
switch (health) {
case 'live':
return 4
case 'risk':
return 3
case 'captcha':
return 2
case 'expired':
return 1
default:
return 0
}
}
function accountTimestamp(value: string | null | undefined): number {
return parseTimestamp(value) ?? 0
}
function shouldPreferRuntimeAccount(
candidate: DesktopAccountInfo,
current: DesktopAccountInfo,
): boolean {
const candidateHealthRank = accountHealthRank(candidate.health)
const currentHealthRank = accountHealthRank(current.health)
if (candidateHealthRank !== currentHealthRank) {
return candidateHealthRank > currentHealthRank
}
const candidateVerifiedAt = accountTimestamp(candidate.verified_at)
const currentVerifiedAt = accountTimestamp(current.verified_at)
if (candidateVerifiedAt !== currentVerifiedAt) {
return candidateVerifiedAt > currentVerifiedAt
}
return candidate.sync_version > current.sync_version
}
function runtimePlatformLabel(platform: string): string {
return getAIPlatformCatalogItem(platform)?.label ?? platform
}
@@ -988,17 +1033,7 @@ async function syncAccounts(source: 'startup' | 'manual'): Promise<void> {
...account,
platform_uid: normalizedPlatformUid,
})
if (hasLocalAccountAuthorization(account) && !accountRequiresStrictProbe(account)) {
reconcileTrackedAccountRemoteState(accountIdentity, {
health: account.health,
verifiedAt: account.verified_at,
profile: {
subjectUid: normalizedPlatformUid,
displayName: account.display_name,
avatarUrl: account.avatar_url,
},
})
} else if (accountRequiresLocalAuthorization(account)) {
if (accountRequiresLocalAuthorization(account) && !hasLocalAccountAuthorization(account)) {
markTrackedAccountMissingPartition(accountIdentity)
}
const projected = getProjectedAccountHealth({
@@ -1045,24 +1080,27 @@ async function syncAccounts(source: 'startup' | 'manual'): Promise<void> {
})
const duplicateAccounts: DesktopAccountInfo[] = []
const dedupedAccounts: DesktopAccountInfo[] = []
const seenAccountKeys = new Set<string>()
const seenAccountIndexes = new Map<string, number>()
for (const account of syncedAccounts) {
if (!account) {
continue
}
const identityKey = buildAccountIdentityKey({
platform: account.platform,
platformUid: account.platform_uid,
displayName: account.display_name,
})
if (seenAccountKeys.has(identityKey)) {
duplicateAccounts.push(account)
const identityKey = buildRuntimeAccountDedupeKey(account)
const existingIndex = seenAccountIndexes.get(identityKey)
if (existingIndex !== undefined) {
const existingAccount = dedupedAccounts[existingIndex]
if (shouldPreferRuntimeAccount(account, existingAccount)) {
duplicateAccounts.push(existingAccount)
dedupedAccounts[existingIndex] = account
} else {
duplicateAccounts.push(account)
}
continue
}
seenAccountKeys.add(identityKey)
seenAccountIndexes.set(identityKey, dedupedAccounts.length)
dedupedAccounts.push(account)
}
@@ -1097,6 +1135,8 @@ async function cleanupDuplicateDesktopAccounts(accounts: DesktopAccountInfo[]):
for (const account of accounts) {
try {
await deleteDesktopAccount(account.id, account.sync_version)
await clearSessionHandle(account.id)
forgetTrackedAccountHealth(account.id)
} catch (error) {
console.warn('[desktop-runtime] duplicate desktop account cleanup failed', {
accountId: account.id,
@@ -1331,9 +1371,14 @@ export function noteRuntimeAccountBound(account: DesktopAccountInfo): void {
const existingIndex = state.accounts.findIndex(
(item) =>
item.id === normalizedAccount.id ||
(isAIPlatformId(normalizedAccount.platform) && item.platform === normalizedAccount.platform) ||
(item.platform === normalizedAccount.platform &&
sameAccountPlatformUid(item.platform_uid, normalizedAccount.platform_uid)),
)
const replacedAccount =
existingIndex >= 0 && state.accounts[existingIndex].id !== normalizedAccount.id
? state.accounts[existingIndex]
: null
if (existingIndex >= 0) {
state.accounts = state.accounts.map((item, index) =>
@@ -1348,6 +1393,19 @@ export function noteRuntimeAccountBound(account: DesktopAccountInfo): void {
displayName: normalizedAccount.display_name,
avatarUrl: normalizedAccount.avatar_url,
})
if (replacedAccount) {
state.accountProfiles.delete(replacedAccount.id)
forgetTrackedAccountHealth(replacedAccount.id)
void clearSessionHandle(replacedAccount.id)
void deleteDesktopAccount(replacedAccount.id, replacedAccount.sync_version).catch((error) => {
console.warn('[desktop-runtime] replaced desktop account cleanup failed', {
accountId: replacedAccount.id,
platform: replacedAccount.platform,
platformUid: replacedAccount.platform_uid,
message: errorMessage(error),
})
})
}
state.lastAccountsSyncAt = Date.now()
syncTrackedAccounts(
state.accounts