diff --git a/apps/desktop-client/src/main/monitor-account-pool.test.ts b/apps/desktop-client/src/main/monitor-account-pool.test.ts index 2d241c1..d12e659 100644 --- a/apps/desktop-client/src/main/monitor-account-pool.test.ts +++ b/apps/desktop-client/src/main/monitor-account-pool.test.ts @@ -40,6 +40,36 @@ describe('monitor account pool', () => { ]) }) + it('allows an explicitly requested challenge retry without admitting expired accounts', () => { + const entries = [ + { + id: 'challenge-retry', + authState: 'challenge_required', + hasLocalAuthorization: true, + allowForcedAttempt: true, + }, + { id: 'challenge-blocked', authState: 'challenge_required', hasLocalAuthorization: true }, + { + id: 'expired', + authState: 'expired', + hasLocalAuthorization: true, + allowForcedAttempt: true, + }, + { + id: 'expiring-retry', + authState: 'expiring_soon', + hasLocalAuthorization: true, + allowForcedAttempt: true, + }, + ] + coolDownMonitorAccount('challenge-retry', 'challenge_required', 1_000) + + expect(selectMonitorAccountCandidates(entries, null, 1_001).map((item) => item.id)).toEqual([ + 'challenge-retry', + 'expiring-retry', + ]) + }) + it('releases a temporarily cooling account after its cooldown', () => { const entries = [{ id: 'account', authState: 'active', hasLocalAuthorization: true }] coolDownMonitorAccount('account', 'adapter_failure', 10_000) diff --git a/apps/desktop-client/src/main/monitor-account-pool.ts b/apps/desktop-client/src/main/monitor-account-pool.ts index e7bbd64..4d1b059 100644 --- a/apps/desktop-client/src/main/monitor-account-pool.ts +++ b/apps/desktop-client/src/main/monitor-account-pool.ts @@ -13,6 +13,7 @@ export type MonitorAccountPoolEntry = { id: string authState: string hasLocalAuthorization: boolean + allowForcedAttempt?: boolean } type MonitorAccountCooldown = { @@ -55,8 +56,9 @@ export function selectMonitorAccountCandidates( .filter( (entry) => entry.hasLocalAuthorization && - !isMonitorAccountAuthBlocked(entry.authState) && - (cooldowns.get(entry.id)?.until ?? 0) <= now, + (!isMonitorAccountAuthBlocked(entry.authState) || + (entry.authState === 'challenge_required' && entry.allowForcedAttempt === true)) && + ((cooldowns.get(entry.id)?.until ?? 0) <= now || entry.allowForcedAttempt === true), ) .sort((left, right) => { if (left.id === preferredAccountId && right.id !== preferredAccountId) { diff --git a/apps/desktop-client/src/main/monitor-platform-breaker.test.ts b/apps/desktop-client/src/main/monitor-platform-breaker.test.ts new file mode 100644 index 0000000..677f998 --- /dev/null +++ b/apps/desktop-client/src/main/monitor-platform-breaker.test.ts @@ -0,0 +1,39 @@ +import { beforeEach, describe, expect, it } from 'vitest' + +import { + clearMonitorPlatformBreaker, + isMonitorPlatformBreakerActive, + resetMonitorPlatformBreakers, + tripMonitorPlatformBreaker, +} from './monitor-platform-breaker' + +describe('monitor platform business-day breaker', () => { + beforeEach(() => resetMonitorPlatformBreakers()) + + it('blocks the challenged platform for the same business day', () => { + tripMonitorPlatformBreaker('doubao', '2026-07-21') + + expect(isMonitorPlatformBreakerActive('doubao', '2026-07-21')).toBe(true) + expect(isMonitorPlatformBreakerActive('qwen', '2026-07-21')).toBe(false) + }) + + it('expires automatically on the next business day', () => { + tripMonitorPlatformBreaker('doubao', '2026-07-21') + + expect(isMonitorPlatformBreakerActive('doubao', '2026-07-22')).toBe(false) + expect(isMonitorPlatformBreakerActive('doubao', '2026-07-22')).toBe(false) + }) + + it('only applies to the exact business day recorded by the failed task', () => { + tripMonitorPlatformBreaker('doubao', '2026-07-22') + + expect(isMonitorPlatformBreakerActive('doubao', '2026-07-21')).toBe(false) + }) + + it('can be cleared immediately after a successful forced collection', () => { + tripMonitorPlatformBreaker('doubao', '2026-07-22') + clearMonitorPlatformBreaker('doubao') + + expect(isMonitorPlatformBreakerActive('doubao', '2026-07-22')).toBe(false) + }) +}) diff --git a/apps/desktop-client/src/main/monitor-platform-breaker.ts b/apps/desktop-client/src/main/monitor-platform-breaker.ts new file mode 100644 index 0000000..b212898 --- /dev/null +++ b/apps/desktop-client/src/main/monitor-platform-breaker.ts @@ -0,0 +1,35 @@ +const blockedBusinessDates = new Map() + +export function tripMonitorPlatformBreaker(platform: string, businessDate: string): void { + const normalizedPlatform = platform.trim() + const normalizedBusinessDate = businessDate.trim() + if (!normalizedPlatform || !normalizedBusinessDate) { + return + } + blockedBusinessDates.set(normalizedPlatform, normalizedBusinessDate) +} + +export function isMonitorPlatformBreakerActive( + platform: string, + currentBusinessDate: string, +): boolean { + const normalizedPlatform = platform.trim() + const normalizedCurrentBusinessDate = currentBusinessDate.trim() + const blockedBusinessDate = blockedBusinessDates.get(normalizedPlatform) + if (!blockedBusinessDate) { + return false + } + if (blockedBusinessDate !== normalizedCurrentBusinessDate) { + blockedBusinessDates.delete(normalizedPlatform) + return false + } + return true +} + +export function clearMonitorPlatformBreaker(platform: string): void { + blockedBusinessDates.delete(platform.trim()) +} + +export function resetMonitorPlatformBreakers(): void { + blockedBusinessDates.clear() +} diff --git a/apps/desktop-client/src/main/monitor-scheduler.test.ts b/apps/desktop-client/src/main/monitor-scheduler.test.ts index 4800e9e..bc7e83f 100644 --- a/apps/desktop-client/src/main/monitor-scheduler.test.ts +++ b/apps/desktop-client/src/main/monitor-scheduler.test.ts @@ -10,6 +10,7 @@ vi.mock('electron/main', () => ({ })) import { + buildMonitorLeasePullPlan, cancelPendingMonitorTasksForPlatform, enqueueMonitorLeaseTask, getMonitorSchedulerSnapshot, @@ -135,12 +136,86 @@ describe('monitor scheduler', () => { ).toBe('collect-now') }) + it('lets collect-now high lane bypass a blocked platform while normal tasks remain blocked', () => { + enqueueMonitorLeaseTask({ + taskId: 'blocked-normal', + clientId: 'client-1', + platform: 'doubao', + routing: 'rabbitmq-primary', + priority: 100, + lane: 'normal', + questionKey: 'q:normal', + }) + enqueueMonitorLeaseTask({ + taskId: 'collect-now', + clientId: 'client-1', + platform: 'doubao', + routing: 'rabbitmq-primary', + priority: 5000, + lane: 'high', + questionKey: 'q:collect-now', + }) + + expect( + selectNextMonitorTask({ + maxConcurrency: 2, + activePlatforms: new Set(), + activeQuestionKeys: new Set(), + overrideableBlockedPlatforms: new Set(['doubao']), + activeCount: 0, + })?.taskId, + ).toBe('collect-now') + }) + + it('does not let collect-now bypass a hard platform block', () => { + enqueueMonitorLeaseTask({ + taskId: 'collect-now', + clientId: 'client-1', + platform: 'doubao', + routing: 'rabbitmq-primary', + priority: 5000, + lane: 'high', + questionKey: 'q:collect-now', + }) + + expect( + selectNextMonitorTask({ + maxConcurrency: 2, + activePlatforms: new Set(), + activeQuestionKeys: new Set(), + blockedPlatforms: new Set(['doubao']), + activeCount: 0, + }), + ).toBeNull() + }) + + it('probes high lane before regular work when a platform is challenge-blocked', () => { + expect( + buildMonitorLeasePullPlan({ + highPrioritySignalPending: false, + hasOverrideableBlockedPlatforms: true, + hasRegularEligiblePlatforms: true, + }), + ).toEqual(['high', 'regular']) + }) + + it('uses the regular ordered lease when no challenge block or signal exists', () => { + expect( + buildMonitorLeasePullPlan({ + highPrioritySignalPending: false, + hasOverrideableBlockedPlatforms: false, + hasRegularEligiblePlatforms: true, + }), + ).toEqual(['regular']) + }) + it('removes later same-platform tasks after the only account hits a challenge', () => { enqueueMonitorLeaseTask({ taskId: 'doubao-current', clientId: 'client-1', platform: 'doubao', routing: 'rabbitmq-primary', + businessDate: '2026-06-22', questionKey: 'q:current', }) enqueueMonitorLeaseTask({ @@ -148,8 +223,17 @@ describe('monitor scheduler', () => { clientId: 'client-1', platform: 'doubao', routing: 'rabbitmq-primary', + businessDate: '2026-06-22', questionKey: 'q:next', }) + enqueueMonitorLeaseTask({ + taskId: 'doubao-tomorrow', + clientId: 'client-1', + platform: 'doubao', + routing: 'rabbitmq-primary', + businessDate: '2026-06-23', + questionKey: 'q:tomorrow', + }) enqueueMonitorLeaseTask({ taskId: 'qwen-next', clientId: 'client-1', @@ -159,14 +243,14 @@ describe('monitor scheduler', () => { }) noteMonitorTaskActivated('doubao-current') - expect(cancelPendingMonitorTasksForPlatform('doubao', 'doubao-current')).toEqual([ + expect(cancelPendingMonitorTasksForPlatform('doubao', 'doubao-current', '2026-06-22')).toEqual([ expect.objectContaining({ taskId: 'doubao-next', platform: 'doubao' }), ]) expect( getMonitorSchedulerSnapshot() .tasks.map((task) => task.taskId) .sort(), - ).toEqual(['doubao-current', 'qwen-next']) + ).toEqual(['doubao-current', 'doubao-tomorrow', 'qwen-next']) expect( selectNextMonitorTask({ maxConcurrency: 2, diff --git a/apps/desktop-client/src/main/monitor-scheduler.ts b/apps/desktop-client/src/main/monitor-scheduler.ts index 652f83d..8901374 100644 --- a/apps/desktop-client/src/main/monitor-scheduler.ts +++ b/apps/desktop-client/src/main/monitor-scheduler.ts @@ -65,9 +65,27 @@ interface MonitorSchedulerSelectionOptions { activePlatforms: ReadonlySet activeQuestionKeys: ReadonlySet blockedPlatforms?: ReadonlySet + overrideableBlockedPlatforms?: ReadonlySet activeCount: number } +export type MonitorLeasePullStep = 'high' | 'regular' + +export function buildMonitorLeasePullPlan(input: { + highPrioritySignalPending: boolean + hasOverrideableBlockedPlatforms: boolean + hasRegularEligiblePlatforms: boolean +}): MonitorLeasePullStep[] { + const plan: MonitorLeasePullStep[] = [] + if (input.highPrioritySignalPending || input.hasOverrideableBlockedPlatforms) { + plan.push('high') + } + if (input.hasRegularEligiblePlatforms) { + plan.push('regular') + } + return plan +} + interface MonitorTaskMetadata { title: string | null businessDate: string | null @@ -477,8 +495,10 @@ export function noteMonitorTaskCanceled(taskId: string): void { export function cancelPendingMonitorTasksForPlatform( platform: string, currentTaskId?: string | null, + businessDate?: string | null, ): MonitorSchedulerTaskRecord[] { const normalizedPlatform = platform.trim() + const normalizedBusinessDate = businessDate?.trim() || null if (!normalizedPlatform) { return [] } @@ -489,7 +509,8 @@ export function cancelPendingMonitorTasksForPlatform( if ( task.platform !== normalizedPlatform || taskId === currentTaskId || - task.state === 'active' + task.state === 'active' || + (normalizedBusinessDate !== null && task.businessDate !== normalizedBusinessDate) ) { continue } @@ -531,15 +552,19 @@ export function selectNextMonitorTask( }) for (const candidate of candidates) { + const isCollectNow = candidate.lane === 'high' if (options.blockedPlatforms?.has(candidate.platform)) { continue } + if (options.overrideableBlockedPlatforms?.has(candidate.platform) && !isCollectNow) { + continue + } if (options.activePlatforms.has(candidate.platform)) { continue } - const bypassQuestionCooldown = candidate.lane === 'high' + const bypassQuestionCooldown = isCollectNow const platformCooldown = draft.platformCooldowns[candidate.platform] ?? 0 if (platformCooldown > now) { diff --git a/apps/desktop-client/src/main/runtime-controller.ts b/apps/desktop-client/src/main/runtime-controller.ts index da097ba..6de8f90 100644 --- a/apps/desktop-client/src/main/runtime-controller.ts +++ b/apps/desktop-client/src/main/runtime-controller.ts @@ -30,6 +30,7 @@ import { getAccountHealthSnapshot, getProjectedAccountHealth, markTrackedAccountBound, + markTrackedAccountExecutionSucceeded, markTrackedAccountMissingPartition, probeTrackedAccount, reportAccountFailure, @@ -71,6 +72,13 @@ import { type MonitorAccountFailoverReason, } from './monitor-account-pool' import { + clearMonitorPlatformBreaker, + isMonitorPlatformBreakerActive, + resetMonitorPlatformBreakers, + tripMonitorPlatformBreaker, +} from './monitor-platform-breaker' +import { + buildMonitorLeasePullPlan, cancelPendingMonitorTasksForPlatform, enqueueMonitorLeaseTask, getMonitorSchedulerSnapshot, @@ -192,8 +200,6 @@ const accountHealthReportDebounceMs = 1_000 const accountHealthReportMaxBatchSize = 100 const maxPlaywrightCDPSoftRecoveries = 3 const playwrightCDPFatalMarker = '--geo-cdp-fatal' -const monitorPlatformCircuitBreakers = new Set() - interface RuntimeTaskRecord { id: string jobId: string @@ -286,6 +292,7 @@ interface RuntimeState { leaseInFlightKinds: Set<'publish' | 'monitor'> publishFallbackBackoffUntil: number monitorFallbackBackoffUntil: number + monitorHighPriorityPullRequested: boolean heartbeatTimer: ReturnType | null pullTimer: ReturnType | null accountSyncTimer: ReturnType | null @@ -320,6 +327,7 @@ const state: RuntimeState = { leaseInFlightKinds: new Set<'publish' | 'monitor'>(), publishFallbackBackoffUntil: 0, monitorFallbackBackoffUntil: 0, + monitorHighPriorityPullRequested: false, heartbeatTimer: null, pullTimer: null, accountSyncTimer: null, @@ -473,6 +481,26 @@ function accountActionRequiredSummary( return `${label} 账号需要完成人机验证后才能继续执行。` } +function accountChallengeBusinessDate(input: { + lastAuthFailureAt: number | null + lastVerifiedAt: number | null +}): string | null { + const timestamp = input.lastAuthFailureAt ?? input.lastVerifiedAt + return timestamp ? currentMonitoringBusinessDate(new Date(timestamp)) : null +} + +function isPriorBusinessDayChallenge(input: { + authState: string + lastAuthFailureAt: number | null + lastVerifiedAt: number | null +}): boolean { + if (input.authState !== 'challenge_required') { + return false + } + const challengeDate = accountChallengeBusinessDate(input) + return Boolean(challengeDate && challengeDate < currentMonitoringBusinessDate()) +} + function monitorPlatformAccountBlockedReason(platform: string): string | null { const ownedAccounts = state.accounts.filter( (item) => item.platform === platform && isAccountOwnedByCurrentClient(item), @@ -496,6 +524,9 @@ function monitorPlatformAccountBlockedReason(platform: string): string | null { if (!isMonitorAccountAuthBlocked(projected.authState)) { return null } + if (isPriorBusinessDayChallenge(projected)) { + return null + } firstBlockedReason ??= projected.authState === 'challenge_required' ? accountActionRequiredSummary(account.platform, projected.authReason) @@ -505,7 +536,7 @@ function monitorPlatformAccountBlockedReason(platform: string): string | null { } function monitorPlatformBlockedReason(platform: string): string | null { - if (monitorPlatformCircuitBreakers.has(platform)) { + if (isMonitorPlatformBreakerActive(platform, currentMonitoringBusinessDate())) { return `${runtimePlatformLabel(platform)} 已因账号人机验证暂停,完成验证并刷新账号状态后恢复。` } return monitorPlatformAccountBlockedReason(platform) @@ -892,7 +923,7 @@ function stopRuntime(): void { function clearRuntimeState(): void { initPublishScheduler() - monitorPlatformCircuitBreakers.clear() + resetMonitorPlatformBreakers() state.tasks.clear() state.accounts = [] state.accountProfiles.clear() @@ -910,6 +941,7 @@ function clearRuntimeState(): void { state.lastError = null state.publishFallbackBackoffUntil = 0 state.monitorFallbackBackoffUntil = 0 + state.monitorHighPriorityPullRequested = false setSchedulerQueueDepth(localQueueDepth()) } @@ -1124,6 +1156,7 @@ async function handleMonitoringDispatchSignal(event: DesktopTaskEventMessage): P } if ((event.lane ?? '').trim() === 'high') { + state.monitorHighPriorityPullRequested = true recordActivity( 'info', '收到监控高优先级信号', @@ -1711,7 +1744,7 @@ export async function requestRuntimeAccountProbe( }) if (snapshot.authState === 'active') { clearMonitorAccountCooldown(account.id) - monitorPlatformCircuitBreakers.delete(account.platform) + clearMonitorPlatformBreaker(account.platform) } enqueueAccountHealthReport(account.id) await mirrorProjectedAccountProfile(account.id) @@ -1782,7 +1815,7 @@ export function noteRuntimeAccountBound(account: DesktopAccountInfo): void { avatarUrl: normalizedAccount.avatar_url, }) clearMonitorAccountCooldown(normalizedAccount.id) - monitorPlatformCircuitBreakers.delete(normalizedAccount.platform) + clearMonitorPlatformBreaker(normalizedAccount.platform) enqueueAccountHealthReport(normalizedAccount.id) refreshAccountNames() } @@ -1991,15 +2024,12 @@ function pumpExecutionLoop(): void { } if (canStartAnotherMonitorExecution()) { - const blockedMonitorExecutionPlatforms = new Set([ - ...blockedPlaywrightPlatforms, - ...blockedMonitorPlatforms(), - ]) const selected = selectNextMonitorTask({ maxConcurrency: resolveAdaptiveMonitorConcurrency(), activePlatforms: activeMonitorPlatforms(), activeQuestionKeys: activeMonitorQuestionKeys(), - blockedPlatforms: blockedMonitorExecutionPlatforms, + blockedPlatforms: blockedPlaywrightPlatforms, + overrideableBlockedPlatforms: blockedMonitorPlatforms(), activeCount: activeMonitorCount(), }) if (selected) { @@ -2086,6 +2116,55 @@ async function leaseSpecificTask( } } +function monitorLeasePlatformIDs(includeChallengeBlocked: boolean): string[] { + const hardBlocked = blockedPlaywrightExecutionPlatforms() + const platformIDs = includeChallengeBlocked + ? aiPlatformCatalog.map((platform) => platform.id) + : eligibleMonitorPlatformIds() + return platformIDs.filter((platform) => !hardBlocked.has(platform)) +} + +async function leaseNextMonitorDesktopTask(): Promise { + const allPlatformIDs = monitorLeasePlatformIDs(true) + const eligiblePlatformIDs = monitorLeasePlatformIDs(false) + const hasOverrideableBlockedPlatforms = blockedMonitorPlatforms().size > 0 + const leaseHighPriority = async (): Promise => { + if (allPlatformIDs.length === 0) { + return { task: null } + } + return await leaseDesktopTask({ + kind: 'monitor', + platform_ids: allPlatformIDs, + lanes: ['high'], + }) + } + + const plan = buildMonitorLeasePullPlan({ + highPrioritySignalPending: state.monitorHighPriorityPullRequested, + hasOverrideableBlockedPlatforms, + hasRegularEligiblePlatforms: eligiblePlatformIDs.length > 0, + }) + for (const step of plan) { + if (step === 'high') { + const highPriority = await leaseHighPriority() + if (highPriority.task) { + return highPriority + } + state.monitorHighPriorityPullRequested = false + continue + } + + const regular = await leaseDesktopTask({ + kind: 'monitor', + platform_ids: eligiblePlatformIDs, + }) + if (regular.task) { + return regular + } + } + return { task: null } +} + async function pullNextTask(kind: 'publish' | 'monitor'): Promise { if (kind === 'monitor') { if (!canRunLive(state.session) || isLeaseInFlight('monitor') || playwrightCDPFatal) { @@ -2098,15 +2177,7 @@ async function pullNextTask(kind: 'publish' | 'monitor'): Promise { state.leaseInFlightKinds.add('monitor') try { - const eligiblePlatformIds = eligibleMonitorPlatformIds() - if (eligiblePlatformIds.length === 0) { - state.monitorFallbackBackoffUntil = Date.now() + monitorPullFallbackIntervalMs - return - } - const leased = await leaseDesktopTask({ - kind: 'monitor', - platform_ids: eligiblePlatformIds, - }) + const leased = await leaseNextMonitorDesktopTask() state.lastPullAt = Date.now() state.lastPullStatus = 'success' noteTransportPull(true) @@ -3301,8 +3372,10 @@ function tripMonitorPlatformCircuitBreaker( task: RuntimeTaskRecord, reason: MonitorAccountFailoverReason, ): void { - monitorPlatformCircuitBreakers.add(task.platform) - const canceledTasks = cancelPendingMonitorTasksForPlatform(task.platform, task.id) + const businessDate = + resolveMonitoringBusinessDate(task.payload) ?? currentMonitoringBusinessDate() + tripMonitorPlatformBreaker(task.platform, businessDate) + const canceledTasks = cancelPendingMonitorTasksForPlatform(task.platform, task.id, businessDate) const summary = `${runtimePlatformLabel(task.platform)} 已无其他可用账号,后续 ${canceledTasks.length} 个本地任务已自动取消。` for (const canceledTask of canceledTasks) { @@ -3331,6 +3404,34 @@ function tripMonitorPlatformCircuitBreaker( }) } +function isCollectNowMonitorTask(task: RuntimeTaskRecord): boolean { + return ( + task.payload.lane === 'high' || + task.payload.trigger_source === 'manual' || + (typeof task.payload.priority === 'number' && task.payload.priority >= 5000) + ) +} + +function shouldForceMonitorAccountAttempt( + task: RuntimeTaskRecord, + health: { + authState: string + lastAuthFailureAt: number | null + lastVerifiedAt: number | null + }, +): boolean { + if (isCollectNowMonitorTask(task)) { + return health.authState !== 'expired' && health.authState !== 'revoked' + } + if (health.authState !== 'challenge_required') { + return false + } + const challengeDate = accountChallengeBusinessDate(health) + const taskBusinessDate = + resolveMonitoringBusinessDate(task.payload) ?? currentMonitoringBusinessDate() + return Boolean(challengeDate && challengeDate < taskBusinessDate) +} + function monitorAccountPoolForTask(task: RuntimeTaskRecord): DesktopAccountInfo[] { const platformAccounts = state.accounts.filter((account) => account.platform === task.platform) const selected = selectMonitorAccountCandidates( @@ -3345,6 +3446,7 @@ function monitorAccountPoolForTask(task: RuntimeTaskRecord): DesktopAccountInfo[ id: account.id, authState: projected.authState, hasLocalAuthorization: hasLocalAccountAuthorization(account), + allowForcedAttempt: shouldForceMonitorAccountAttempt(task, projected), } }), task.accountId || null, @@ -3419,8 +3521,15 @@ async function executeMonitorTaskWithAccountPool( } const previousSnapshot = getAccountHealthSnapshot(identity.id) - const readiness = await ensureAccountReady(identity) - if (readiness.authState !== 'active') { + const projected = getProjectedAccountHealth({ + accountId: account.id, + platform: account.platform, + health: account.health, + verifiedAt: account.verified_at, + }) + const forceAccountAttempt = shouldForceMonitorAccountAttempt(attemptTask, projected) + const readiness = forceAccountAttempt ? null : await ensureAccountReady(identity) + if (readiness && readiness.authState !== 'active') { lastResult = buildAccountBlockedResult(attemptTask, readiness) maybeRecordAccountAccessAlert(attemptTask, identity, previousSnapshot, readiness) enqueueAccountHealthReport(identity.id) @@ -3438,6 +3547,11 @@ async function executeMonitorTaskWithAccountPool( } } await maybeReportTaskAuthFailure(attemptTask, lastResult, identity) + if (lastResult.status === 'succeeded') { + markTrackedAccountExecutionSucceeded(identity) + clearMonitorPlatformBreaker(task.platform) + enqueueAccountHealthReport(identity.id) + } } const failoverReason = classifyMonitorAccountFailover(lastResult) diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index 866be95..bc84368 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -346,6 +346,7 @@ export interface LeaseDesktopTaskRequest { task_id?: string kind?: 'publish' | 'monitor' platform_ids?: string[] + lanes?: string[] } export interface LeaseDesktopTaskResponse {