import { cpus, hostname, totalmem } from 'node:os' import { ApiClientError } from '@geo/http-client' import type { DesktopAccountHealthReport, DesktopAccountInfo, DesktopArticleContent, DesktopClientInfo, DesktopRuntimeSessionSyncRequest, DesktopTaskEventMessage, DesktopTaskInfo, JsonValue, LeaseDesktopTaskResponse, MonitoringLeaseTask, MonitoringSourceItem, MonitoringTaskResultPayload, } from '@geo/shared-types' import { aiPlatformCatalog, getAIPlatformCatalogItem, isAIPlatformId } from '@geo/shared-types' import { buildAccountIdentityKey, normalizeAccountPlatformUid, sameAccountPlatformUid, } from '../shared/account-identity' import { normalizeDesktopApiBaseURL } from '../shared/api-base-url' import { type PublishAccountIdentity, type PublishAccountProfile } from './account-binder' import { ensureAccountReady, forgetTrackedAccountHealth, getAccountHealthSnapshot, getProjectedAccountHealth, markTrackedAccountBound, markTrackedAccountMissingPartition, probeTrackedAccount, reportAccountFailure, syncTrackedAccounts, } from './account-health' import { baijiahaoAdapter, bilibiliAdapter, dongchediAdapter, getMonitorAdapter, jianshuAdapter, juejinAdapter, qiehaoAdapter, smzdmAdapter, sohuhaoAdapter, toutiaoAdapter, wangyihaoAdapter, weixinGzhAdapter, zhihuAdapter, zolAdapter, type AdapterExecutionResult, type MonitorAdapter, type PublishAdapter, } from './adapters' import { getDesktopAppVersion, getDesktopReleaseChannel } from './app-version' import { getLeaseManagerSnapshot, noteLeaseExtended, noteLeaseReleased, setActiveLease, } from './lease-manager' import { enqueueMonitorLeaseTask, enqueueMonitorTaskFromEvent, getMonitorSchedulerSnapshot, initMonitorScheduler, noteMonitorTaskActivated, noteMonitorTaskCanceled, noteMonitorTaskCompleted, noteMonitorTaskLeaseMiss, noteMonitorTaskLeased, selectNextMonitorTask, } from './monitor-scheduler' import { retainHiddenPlaywrightPage, startHiddenPlaywrightReaper } from './playwright-cdp' import { getProcessMetricsSnapshot } from './process-metrics' import { enqueuePublishTask as enqueuePublishSchedulerTask, getPublishSchedulerSnapshot, initPublishScheduler, notePublishTaskActivated, notePublishTaskCanceled, notePublishTaskCompleted, notePublishTaskLeaseMiss, selectNextPublishTask, } from './publish-scheduler' import { emitRuntimeInvalidated, onRuntimeInvalidated } from './runtime-events' import { initScheduler, noteSchedulerAccountsSync, noteSchedulerHeartbeat, noteSchedulerPull, noteSchedulerSseEvent, noteSchedulerTaskFinished, setSchedulerCurrentTask, setSchedulerError, setSchedulerNextHeartbeat, setSchedulerNextPull, setSchedulerPhase, setSchedulerQueueDepth, } from './scheduler' import { clearSessionHandle, createSessionHandle, getPersistedPartition, getSessionHandle, } from './session-registry' import { cancelDesktopTask, cancelMonitoringTask, completeDesktopTask, configureTransport, deleteDesktopAccount, extendDesktopTask, getDesktopArticleContent, heartbeatDesktopClient, leaseDesktopTask, leaseMonitoringTasks, listDesktopAccounts, markDesktopTaskPublishSubmitStarted, noteTransportHeartbeat, noteTransportPull, offlineDesktopClient, reportDesktopAccountHealth, resumeMonitoringTasks, revokeDesktopClient, setTransportAuthState, setTransportDispatchState, skipMonitoringTask, submitMonitoringTaskResult, upsertDesktopAccount, } from './transport/api-client' import { DispatchWsClient } from './transport/dispatch-ws-client' import { releaseHotView, retainHotView } from './view-pool' type RuntimeTone = 'info' | 'success' | 'warn' | 'danger' type RuntimeTaskStatus = 'queued' | 'in_progress' | 'succeeded' | 'failed' | 'unknown' | 'aborted' type RuntimeTaskRouting = 'rabbitmq-primary' | 'db-recovery' type MonitorExecutionPhase = | 'PREPARING' | 'NAVIGATING' | 'AUTHENTICATING' | 'INPUTTING' | 'SUBMITTING' | 'WAITING' | 'PARSING' | 'POSTING' const heartbeatIntervalMs = 15_000 const pullIntervalMs = 60_000 const pumpWatchdogIntervalMs = 15_000 const leaseExtendIntervalMs = 60_000 const defaultPublishTaskTimeoutMs = 5 * 60_000 const publishTaskStaleProgressMs = 2 * 60_000 const publishLongRunningNotificationMs = 15 * 60_000 const maxActivityItems = 60 const monitorBusinessTimeZone = 'Asia/Shanghai' const authRequiredMonitorPlatforms = new Set(aiPlatformCatalog.map((platform) => platform.id)) const publishReservedSlots = 1 const minimumForegroundTotalConcurrency = 2 const maximumRuntimeTotalConcurrency = 4 const BAIJIAHAO_RISK_CONTROL_PROMPT = '您触发平台风控,请手动发稿一篇并完成验证,方可继续使用' const accountHealthReportDebounceMs = 1_000 const accountHealthReportMaxBatchSize = 100 interface RuntimeTaskRecord { id: string jobId: string title: string kind: 'publish' | 'monitor' platform: string accountId: string accountName: string clientId: string status: RuntimeTaskStatus routing: RuntimeTaskRouting leaseExpiresAt: number | null startedAt: number | null updatedAt: number summary: string payload: Record error: Record | null result: Record | null attemptId: string | null leaseToken: string | null } export interface RuntimeControllerActivity { id: string severity: RuntimeTone title: string detail: string at: number } export interface RuntimeControllerSnapshot { session: DesktopRuntimeSessionSyncRequest | null client: DesktopClientInfo | null running: boolean dispatchConnected: boolean queueDepth: number accounts: DesktopAccountInfo[] accountProfiles: Record tasks: Array> activity: RuntimeControllerActivity[] lastHeartbeatAt: number lastHeartbeatStatus: 'idle' | 'success' | 'failed' lastPullAt: number lastPullStatus: 'idle' | 'success' | 'failed' lastAccountsSyncAt: number lastServerTime: string | null onlineClientCount: number | null lastError: string | null } interface PendingTaskRequest { taskId: string routing: RuntimeTaskRouting platform?: string source?: 'desktop_task' | 'monitoring_lease' } interface ActiveRuntimeExecution { taskId: string kind: 'publish' | 'monitor' platform: string accountId: string abortController: AbortController executionPhase: MonitorExecutionPhase | null interruptRequested: boolean interruptGeneration: number interruptReason: string | null lastSafePointAt: number | null startedAt: number lastProgressAt: number lastProgressSummary: string | null deadlineAt: number | null deadlineHandle: ReturnType | null longRunningNotifiedAt: number | null // Set the moment a publish adapter enters its irreversible submit/save_draft stage. Once set, // an interrupted publish is completed as 'unknown' (manual reconcile) instead of 'failed', so a // possibly-already-published article is never silently re-posted by a retry. submitStartedAt: number | null } interface RuntimeState { session: DesktopRuntimeSessionSyncRequest | null client: DesktopClientInfo | null running: boolean tasks: Map accounts: DesktopAccountInfo[] accountProfiles: Map activity: RuntimeControllerActivity[] activeExecutions: Map leaseInFlightKinds: Set<'publish' | 'monitor'> publishFallbackBackoffUntil: number heartbeatTimer: ReturnType | null pullTimer: ReturnType | null pumpWatchdogTimer: ReturnType | null accountHealthReportTimer: ReturnType | null accountHealthReportQueue: Map accountHealthReportBadAccounts: Set accountRemoteHealth: Map dispatchWsClient: DispatchWsClient | null dispatchWsConnected: boolean lastHeartbeatAt: number lastHeartbeatStatus: 'idle' | 'success' | 'failed' lastPullAt: number lastPullStatus: 'idle' | 'success' | 'failed' lastAccountsSyncAt: number lastServerTime: string | null onlineClientCount: number | null lastError: string | null activitySeq: number } const state: RuntimeState = { session: null, client: null, running: false, tasks: new Map(), accounts: [], accountProfiles: new Map(), activity: [], activeExecutions: new Map(), leaseInFlightKinds: new Set<'publish' | 'monitor'>(), publishFallbackBackoffUntil: 0, heartbeatTimer: null, pullTimer: null, pumpWatchdogTimer: null, accountHealthReportTimer: null, accountHealthReportQueue: new Map(), accountHealthReportBadAccounts: new Set(), accountRemoteHealth: new Map(), dispatchWsClient: null, dispatchWsConnected: false, lastHeartbeatAt: 0, lastHeartbeatStatus: 'idle', lastPullAt: 0, lastPullStatus: 'idle', lastAccountsSyncAt: 0, lastServerTime: null, onlineClientCount: null, lastError: null, activitySeq: 0, } let accountHealthReportBridgeUnsubscribe: (() => void) | null = null function accountIdentityFromDesktopAccount(account: DesktopAccountInfo): PublishAccountIdentity { return { id: account.id, platform: account.platform, platformUid: normalizeAccountPlatformUid(account.platform_uid) || account.platform_uid, displayName: account.display_name, } } function accountIdentityFromTask(task: RuntimeTaskRecord): PublishAccountIdentity | null { if (!task.accountId) { return null } const matched = state.accounts.find((account) => account.id === task.accountId) ?? null if (matched) { return accountIdentityFromDesktopAccount(matched) } return { id: task.accountId, platform: task.platform, platformUid: '', displayName: task.accountName || '待同步账号', } } function isDefinitiveAuthState(authState: string): boolean { return ['active', 'expired', 'challenge_required', 'revoked'].includes(authState) } function hasLocalAccountSession(accountId: string): boolean { return Boolean(getSessionHandle(accountId) || getPersistedPartition(accountId)) } function isAccountOwnedByCurrentClient(account: DesktopAccountInfo): boolean { return Boolean(account.client_id && account.client_id === state.client?.id) } function hasLocalAccountAuthorization(account: DesktopAccountInfo): boolean { return isAccountOwnedByCurrentClient(account) && hasLocalAccountSession(account.id) } function accountRequiresLocalAuthorization(account: DesktopAccountInfo): boolean { return Boolean(account.client_id) } 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 } function accountActionRequiredSummary( platform: string, authReason: string | null | undefined, ): string { const label = runtimePlatformLabel(platform) if (platform === 'baijiahao') { return BAIJIAHAO_RISK_CONTROL_PROMPT } if (authReason === 'risk_control') { return `${label} 账号疑似触发风控,请打开平台处理验证或等待限制解除后再继续执行。` } return `${label} 账号需要完成人机验证后才能继续执行。` } function markAuthorizationFailureNonRetryable( error: Record, category: 'ai_platform_authorization' | 'media_account_authorization', ): Record { return { ...error, retryable: false, non_retryable: true, failure_category: category, } } function isAuthorizationFailureCode(code: string | null | undefined): boolean { if (!code) { return false } const normalized = code.trim().toLowerCase() return ( normalized === 'desktop_account_auth_expired' || normalized === 'desktop_account_challenge_required' || normalized === 'desktop_account_risk_control' || normalized.endsWith('_not_logged_in') || normalized.endsWith('_login_required') || normalized.endsWith('_login_expired') || normalized.endsWith('_challenge_required') ) } function isAuthorizationFailureMessage(message: string | null | undefined): boolean { if (!message) { return false } const normalized = message.trim().toLowerCase() return ( normalized.includes('login required') || normalized.includes('login expired') || normalized.includes('not logged in') || normalized.includes('unauthorized') || normalized.includes('登录态失效') || normalized.includes('登录态已失效') || normalized.includes('登录已失效') || normalized.includes('请重新登录') || normalized.includes('请先登录') || normalized.includes('未登录') ) } function isAuthorizationFailureCategory(category: string | null | undefined): boolean { return category === 'ai_platform_authorization' || category === 'media_account_authorization' } function isNonRetryableAuthorizationFailure( task: RuntimeTaskRecord, result: AdapterExecutionResult, ): boolean { if (result.status !== 'failed') { return false } const code = typeof result.error?.code === 'string' ? result.error.code : null const message = typeof result.error?.message === 'string' ? result.error.message : null const category = typeof result.error?.failure_category === 'string' ? result.error.failure_category : null if (isAuthorizationFailureCategory(category)) { return true } if (!isAuthorizationFailureCode(code) && !isAuthorizationFailureMessage(message)) { return false } return task.kind === 'publish' || isAIPlatformId(task.platform) } function withAuthorizationRetryPolicy( task: RuntimeTaskRecord, result: AdapterExecutionResult, ): AdapterExecutionResult { if (!isNonRetryableAuthorizationFailure(task, result)) { return result } const category = isAIPlatformId(task.platform) ? 'ai_platform_authorization' : 'media_account_authorization' const baseError = result.error ?? { code: 'desktop_account_authorization_failed', message: result.summary, } return { ...result, error: markAuthorizationFailureNonRetryable(baseError, category), } } async function resolveAuthorizationPreflightBlock( task: RuntimeTaskRecord, ): Promise { const accountIdentity = accountIdentityFromTask(task) if (!accountIdentity || !shouldEnforceActiveAccount(task)) { return null } const previousSnapshot = getAccountHealthSnapshot(accountIdentity.id) const readiness = await ensureAccountReady(accountIdentity) if (readiness.authState === 'active') { return null } const result = withAuthorizationRetryPolicy(task, buildAccountBlockedResult(task, readiness)) maybeRecordAccountAccessAlert(task, accountIdentity, previousSnapshot, readiness) enqueueAccountHealthReport(accountIdentity.id) recordAuthorizationPreflightBlock(task, result) return result } function recordAuthorizationPreflightBlock( task: RuntimeTaskRecord, result: AdapterExecutionResult, ): void { if (!isNonRetryableAuthorizationFailure(task, result)) { return } const scope = task.kind === 'monitor' && isAIPlatformId(task.platform) ? '同模型' : '同账号' recordActivity( 'warn', '授权失效,任务未执行', `${runtimePlatformLabel(task.platform)} ${scope}授权不是 active,${task.title} 已直接回写 non-retryable 授权失败。`, ) } function maybeRecordAccountAccessAlert( task: RuntimeTaskRecord, accountIdentity: PublishAccountIdentity | null, previous: ReturnType, next: ReturnType, ): void { if (!accountIdentity || !next) { return } if (previous?.authState === next.authState && previous?.authReason === next.authReason) { return } if (next.authState === 'challenge_required' && next.authReason === 'risk_control') { if (accountIdentity.platform === 'baijiahao') { recordActivity('warn', '百家号触发平台风控', BAIJIAHAO_RISK_CONTROL_PROMPT) return } recordActivity( 'warn', `${runtimePlatformLabel(accountIdentity.platform)} 触发风控`, `${accountIdentity.displayName || task.accountName || '当前账号'} 已被平台限制,请打开平台处理验证或等待限制解除后再刷新状态。`, ) return } if (next.authState === 'challenge_required') { if (accountIdentity.platform === 'baijiahao') { recordActivity('warn', '百家号触发平台风控', BAIJIAHAO_RISK_CONTROL_PROMPT) return } recordActivity( 'warn', `${runtimePlatformLabel(accountIdentity.platform)} 需要人工验证`, `${accountIdentity.displayName || task.accountName || '当前账号'} 需要打开平台完成人工验证后再继续执行任务。`, ) } } function isoFromTimestamp(value: number | null): string | null { return value ? new Date(value).toISOString() : null } export function syncRuntimeSession(next: DesktopRuntimeSessionSyncRequest | null): void { const normalized = normalizeSession(next) const changed = !sameSession(state.session, normalized) state.session = normalized state.client = normalized?.desktop_client ?? null if (!canRunLive(normalized)) { stopRuntime() clearRuntimeState() configureTransport(null) setTransportAuthState('pending') return } configureTransport(normalized) setTransportAuthState('registered') if (changed) { stopRuntime() clearRuntimeState() recordActivity( 'info', '任务消费端已就绪', `客户端 ${normalized.desktop_client?.device_name ?? normalized.desktop_client?.id ?? '未知客户端'} 已准备消费桌面任务。`, ) } if (!state.running) { startRuntime() } } export async function releaseRuntimeSession(options: { revoke?: boolean } = {}): Promise { const currentSession = state.session const shouldReleaseRemote = canRunLive(currentSession) if (shouldReleaseRemote) { try { if (options.revoke) { await revokeDesktopClient() } else { await offlineDesktopClient() } } catch (error) { console.warn('[desktop-runtime] release runtime session failed', { revoke: Boolean(options.revoke), message: errorMessage(error), }) } } stopRuntime() clearRuntimeState() configureTransport(null) setTransportAuthState('pending') state.session = null state.client = null } export function getRuntimeControllerSnapshot(): RuntimeControllerSnapshot { return { session: state.session ? { ...state.session } : null, client: state.client ? { ...state.client } : null, running: state.running, dispatchConnected: state.dispatchWsConnected, queueDepth: localQueueDepth(), accounts: state.accounts.map((item) => ({ ...item })), accountProfiles: Object.fromEntries( [...state.accountProfiles.entries()].map(([accountId, profile]) => [ accountId, { ...profile }, ]), ), tasks: [...state.tasks.values()] .sort((left, right) => right.updatedAt - left.updatedAt) .map(({ leaseToken: _leaseToken, ...task }) => ({ ...task, startedAt: state.activeExecutions.get(task.id)?.startedAt ?? task.startedAt, })), activity: [...state.activity], lastHeartbeatAt: state.lastHeartbeatAt, lastHeartbeatStatus: state.lastHeartbeatStatus, lastPullAt: state.lastPullAt, lastPullStatus: state.lastPullStatus, lastAccountsSyncAt: state.lastAccountsSyncAt, lastServerTime: state.lastServerTime, onlineClientCount: state.onlineClientCount, lastError: state.lastError, } } function startRuntime(): void { if (!canRunLive(state.session) || state.running) { return } initScheduler() initMonitorScheduler() initPublishScheduler() startHiddenPlaywrightReaper() ensureAccountHealthReportBridge() state.running = true setSchedulerPhase('running') setSchedulerError(null) recordActivity('success', '调度节点已启动', '已启动 WebSocket 分发、心跳上报和兜底拉取。') connectDispatchWs() scheduleHeartbeatLoop() schedulePullLoop() schedulePumpWatchdogLoop() void sendHeartbeat('startup') void syncAccounts('startup') void resumeLeasedMonitoringTasks('startup') pumpExecutionLoop() } function ensureAccountHealthReportBridge(): void { if (accountHealthReportBridgeUnsubscribe) { return } accountHealthReportBridgeUnsubscribe = onRuntimeInvalidated((event) => { if (event.reason !== 'account-health' || !event.accountId) { return } enqueueAccountHealthReport(event.accountId) }) } function stopRuntime(): void { state.running = false state.leaseInFlightKinds.clear() for (const execution of state.activeExecutions.values()) { if (execution.deadlineHandle) { clearTimeout(execution.deadlineHandle) execution.deadlineHandle = null } execution.abortController.abort() } state.activeExecutions.clear() if (state.heartbeatTimer) { clearInterval(state.heartbeatTimer) state.heartbeatTimer = null } if (state.pullTimer) { clearInterval(state.pullTimer) state.pullTimer = null } if (state.pumpWatchdogTimer) { clearInterval(state.pumpWatchdogTimer) state.pumpWatchdogTimer = null } if (state.accountHealthReportTimer) { clearTimeout(state.accountHealthReportTimer) state.accountHealthReportTimer = null } if (state.dispatchWsClient) { state.dispatchWsClient.stop() state.dispatchWsClient = null } state.dispatchWsConnected = false setTransportDispatchState('idle') setSchedulerPhase('idle') setSchedulerCurrentTask(null) setSchedulerQueueDepth(localQueueDepth()) setSchedulerNextHeartbeat(null) setSchedulerNextPull(null) if (getLeaseManagerSnapshot().activeTaskId) { noteLeaseReleased('cleared') } } function clearRuntimeState(): void { initPublishScheduler() state.tasks.clear() state.accounts = [] state.accountProfiles.clear() state.accountHealthReportQueue.clear() state.accountHealthReportBadAccounts.clear() state.accountRemoteHealth.clear() state.activeExecutions.clear() state.lastHeartbeatAt = 0 state.lastHeartbeatStatus = 'idle' state.lastPullAt = 0 state.lastPullStatus = 'idle' state.lastAccountsSyncAt = 0 state.lastServerTime = null state.onlineClientCount = null state.lastError = null state.publishFallbackBackoffUntil = 0 setSchedulerQueueDepth(localQueueDepth()) } function scheduleHeartbeatLoop(): void { if (state.heartbeatTimer) { clearInterval(state.heartbeatTimer) } setSchedulerNextHeartbeat(Date.now() + heartbeatIntervalMs) state.heartbeatTimer = setInterval(() => { setSchedulerNextHeartbeat(Date.now() + heartbeatIntervalMs) void sendHeartbeat('timer') }, heartbeatIntervalMs) } function schedulePullLoop(): void { if (state.pullTimer) { clearInterval(state.pullTimer) } setSchedulerNextPull(Date.now() + pullIntervalMs) state.pullTimer = setInterval(() => { setSchedulerNextPull(Date.now() + pullIntervalMs) pumpExecutionLoop() }, pullIntervalMs) } function schedulePumpWatchdogLoop(): void { if (state.pumpWatchdogTimer) { clearInterval(state.pumpWatchdogTimer) } state.pumpWatchdogTimer = setInterval(() => { notifyLongRunningPublishTasks() pumpExecutionLoop() }, pumpWatchdogIntervalMs) } function connectDispatchWs(): void { if (!canRunLive(state.session)) { return } state.dispatchWsClient?.stop() setTransportDispatchState('connecting') const wsUrl = buildApiUrl(state.session.api_base_url, '/api/desktop/dispatch') .replace(/^http:\/\//, 'ws://') .replace(/^https:\/\//, 'wss://') const client = new DispatchWsClient({ url: wsUrl, headers: { Authorization: `Bearer ${state.session.client_token}`, }, }) let hasConnectedOnce = false client.on('open', () => { state.dispatchWsConnected = true setTransportDispatchState('streaming') setSchedulerError(null) recordActivity('success', '任务分发通道已连接', '统一 WebSocket 分发通道已就绪。') void pullNextTask('monitor') if (hasConnectedOnce) { void resumeLeasedMonitoringTasks('reconnect') } hasConnectedOnce = true }) client.on('connected', (payload) => { state.dispatchWsConnected = true setTransportDispatchState('streaming') if (isConnectedEvent(payload)) { state.lastServerTime = payload.server_time } }) const dispatchTaskHandler = (payload: unknown) => { state.dispatchWsConnected = true noteSchedulerSseEvent() if (isMonitoringTaskControlEvent(payload)) { void handleMonitoringTaskControl(payload) return } if (isMonitoringDispatchSignal(payload)) { void handleMonitoringDispatchSignal(payload) return } if (!isDesktopTaskEvent(payload)) { return } handleTaskEvent(payload) } client.on('task_available', dispatchTaskHandler) client.on('task_leased', dispatchTaskHandler) client.on('task_extended', dispatchTaskHandler) client.on('task_completed', dispatchTaskHandler) client.on('task_canceled', dispatchTaskHandler) client.on('task_reconciled', dispatchTaskHandler) client.on('task_control', dispatchTaskHandler) client.on('error', (payload) => { state.dispatchWsConnected = false setTransportDispatchState('connecting') const message = errorMessage(payload) state.lastError = message setSchedulerError(message) recordActivity('warn', '任务分发通道异常', message) }) client.on('close', () => { state.dispatchWsConnected = false setTransportDispatchState(state.running ? 'connecting' : 'idle') }) client.on('reconnect', (payload) => { if (!isReconnectPayload(payload)) { return } setTransportDispatchState('connecting') recordActivity('info', '任务分发通道退避等待', `将在 ${payload.delayMs}ms 后重连 WebSocket。`) }) state.dispatchWsClient = client client.start() } function handleTaskEvent(event: DesktopTaskEventMessage): void { if (event.type === 'task_control' || event.signal_only || !event.task_id.trim()) { return } const existing = state.tasks.get(event.task_id) const eventUpdatedAt = parseTimestamp(event.updated_at) ?? Date.now() if (existing && eventUpdatedAt < existing.updatedAt) { return } const next: RuntimeTaskRecord = { id: event.task_id, jobId: event.job_id, title: existing?.title ?? defaultTaskTitle(event.kind, event.platform || existing?.platform), kind: event.kind, platform: event.platform || existing?.platform || inferPlatformFromAccount(event.target_account_id), accountId: event.target_account_id || existing?.accountId || '', accountName: existing?.accountName ?? '待同步账号', clientId: event.target_client_id, status: event.status, routing: existing?.routing ?? 'rabbitmq-primary', leaseExpiresAt: event.status === 'in_progress' ? (existing?.leaseExpiresAt ?? null) : null, startedAt: event.status === 'in_progress' ? (existing?.startedAt ?? eventUpdatedAt) : null, updatedAt: eventUpdatedAt, summary: summaryFromEventType(event.type, event.status), payload: existing?.payload ?? {}, error: existing?.error ?? null, result: existing?.result ?? null, attemptId: event.status === 'in_progress' ? (existing?.attemptId ?? null) : null, leaseToken: event.status === 'in_progress' ? (existing?.leaseToken ?? null) : null, } state.tasks.set(event.task_id, next) if (event.kind === 'monitor') { if (event.type === 'task_available') { enqueueMonitorTaskFromEvent(event, 'rabbitmq-primary') } if ( event.type === 'task_completed' || event.type === 'task_reconciled' || event.type === 'task_canceled' ) { noteMonitorTaskCanceled(event.task_id) } } if ( event.kind === 'publish' && (event.type === 'task_completed' || event.type === 'task_reconciled' || event.type === 'task_canceled') ) { notePublishTaskCanceled(event.task_id) } if (event.type === 'task_available') { if (event.kind === 'publish') { enqueuePublishTask(next.id, next.platform, 'rabbitmq-primary') } pumpExecutionLoop() } } async function handleMonitoringDispatchSignal(event: DesktopTaskEventMessage): Promise { if (event.target_client_id !== currentClientID()) { return } if ((event.lane ?? '').trim() === 'high') { recordActivity( 'info', '收到监控高优先级信号', '客户端将按平台隔离和并发上限,优先领取 collect-now 桌面监控任务。', ) } if (event.task_id.trim()) { enqueueMonitorTaskFromEvent(event, 'rabbitmq-primary') pumpExecutionLoop() return } await pullNextTask('monitor') } async function handleMonitoringTaskControl(event: DesktopTaskEventMessage): Promise { if ( event.target_client_id !== currentClientID() || event.control !== 'interrupt_requested' || !event.task_id.trim() ) { return } const taskId = event.task_id.trim() const generation = toInterruptGeneration(event.interrupt_generation) const existing = state.tasks.get(taskId) ?? null if (existing && generation < toInterruptGeneration(existing.payload.interrupt_generation)) { return } const active = state.activeExecutions.get(taskId) if (active?.kind === 'monitor') { if (isDesktopTaskID(taskId)) { if (existing) { existing.summary = '已收到立即收集请求;当前执行继续保留,high lane 任务会按平台隔离和并发上限调度。' existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } recordActivity( 'info', '监控任务保留当前执行', `${existing?.title ?? taskId} 当前执行继续保留;若 high lane 属于其他平台可使用第二个窗口并行,否则等待当前平台释放。`, ) return } if (existing) { existing.summary = '已收到抢占请求;当前监控执行将自然完成,随后优先处理 high lane 任务。' existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } recordActivity( 'warn', '监控任务收到抢占请求', `${existing?.title ?? taskId} 当前已在执行,Phase 1 将等待自然完成。`, ) return } if (existing) { existing.payload = { ...existing.payload, interrupt_requested: true, interrupt_generation: generation, interrupt_reason: event.reason?.trim() || 'collect_now_preempt', replacement_task_id: event.replacement_task_id?.trim() || null, } existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } if (!existing?.leaseToken) { noteMonitorTaskCanceled(taskId) if (existing) { existing.status = 'aborted' existing.summary = '已收到抢占请求,本地排队中的旧监控任务已让路。' existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } await pullMonitoringTasks('rabbitmq-primary') return } const monitoringTaskID = Number.parseInt(taskId, 10) if (!Number.isFinite(monitoringTaskID)) { noteMonitorTaskCanceled(taskId) if (existing) { existing.status = 'aborted' existing.leaseToken = null existing.summary = '已收到抢占请求,但任务不是 legacy monitoring lease,已从本地调度队列移除。' existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } await pullMonitoringTasks('rabbitmq-primary') return } try { await cancelMonitoringTask(monitoringTaskID, { lease_token: existing.leaseToken, reason: event.reason?.trim() || 'collect_now_preempt', }) noteMonitorTaskCanceled(taskId) existing.status = 'aborted' existing.leaseToken = null existing.summary = '已响应抢占请求,释放旧监控租约并为高优任务让路。' existing.updatedAt = Date.now() state.tasks.set(taskId, existing) recordActivity('info', '已取消旧监控租约', `${existing.title} 已从本地排队队列中移除。`) await pullMonitoringTasks('rabbitmq-primary') } catch (error) { const message = errorMessage(error) if (existing) { existing.summary = `收到抢占请求,但取消租约失败:${message}` existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } recordActivity( 'warn', '监控租约抢占失败', `${existing?.title ?? taskId} 取消租约失败:${message}`, ) } } async function sendHeartbeat(source: 'startup' | 'timer'): Promise { if (!canRunLive(state.session)) { return } const previousStatus = state.lastHeartbeatStatus try { const response = await heartbeatDesktopClient({ device_name: hostname() || state.client?.device_name || `Desktop ${process.platform}`, os: process.platform, cpu_arch: process.arch, client_version: getDesktopAppVersion(), channel: state.client?.channel ?? getDesktopReleaseChannel(), startup: source === 'startup', account_ids: state.accounts .filter((account) => shouldReportAccountPresence(account)) .map((account) => account.id), }) state.client = response.client state.lastHeartbeatAt = Date.now() state.lastHeartbeatStatus = 'success' state.lastServerTime = response.server_time state.onlineClientCount = response.current_user_online_client_count ?? response.online_client_count ?? null state.lastError = null noteTransportHeartbeat(true) noteSchedulerHeartbeat() setSchedulerError(null) if (source === 'startup' || previousStatus === 'failed') { recordActivity('success', '心跳连接正常', '客户端心跳已回写到服务端。') } } catch (error) { state.lastHeartbeatAt = Date.now() state.lastHeartbeatStatus = 'failed' state.lastError = errorMessage(error) noteTransportHeartbeat(false) setSchedulerError(state.lastError) if (source === 'startup' || previousStatus !== 'failed') { recordActivity('danger', '心跳连接失败', state.lastError) } } } async function syncAccounts(source: 'startup' | 'manual'): Promise { if (!canRunLive(state.session)) { return } try { const accounts = await listDesktopAccounts() state.lastAccountsSyncAt = Date.now() state.lastError = null noteSchedulerAccountsSync() setSchedulerError(null) const identities = accounts .filter((account) => hasLocalAccountAuthorization(account)) .map((account) => accountIdentityFromDesktopAccount(account)) syncTrackedAccounts(identities) state.accountRemoteHealth = new Map(accounts.map((account) => [account.id, account.health])) state.accountProfiles.clear() const syncedAccounts: Array = accounts.map((account) => { const normalizedPlatformUid = normalizeAccountPlatformUid(account.platform_uid) || account.platform_uid const isAIPlatform = isAIPlatformId(account.platform) const accountIdentity = accountIdentityFromDesktopAccount({ ...account, platform_uid: normalizedPlatformUid, }) if (accountRequiresLocalAuthorization(account) && !hasLocalAccountAuthorization(account)) { markTrackedAccountMissingPartition(accountIdentity) } const projected = getProjectedAccountHealth({ accountId: account.id, platform: account.platform, health: account.health, verifiedAt: account.verified_at, }) if (projected.profile?.subjectUid && projected.profile.displayName) { state.accountProfiles.set(account.id, { platformUid: projected.profile.subjectUid, displayName: projected.profile.displayName, avatarUrl: projected.profile.avatarUrl, }) } const normalizedLocalPlatformUid = projected.profile?.subjectUid ? normalizeAccountPlatformUid(projected.profile.subjectUid) || projected.profile.subjectUid : null const hasPlatformUidMismatch = Boolean( normalizedLocalPlatformUid && !sameAccountPlatformUid(normalizedLocalPlatformUid, normalizedPlatformUid), ) if (hasPlatformUidMismatch && !isAIPlatform) { return { ...account, platform_uid: normalizedPlatformUid, health: 'expired' as const, } } const resolvedAccount: DesktopAccountInfo = { ...account, platform_uid: normalizedPlatformUid, display_name: projected.profile?.displayName ?? account.display_name, avatar_url: projected.profile?.avatarUrl ?? account.avatar_url, health: projected.health, verified_at: isoFromTimestamp(projected.lastVerifiedAt) ?? account.verified_at, } return resolvedAccount }) const duplicateAccounts: DesktopAccountInfo[] = [] const dedupedAccounts: DesktopAccountInfo[] = [] const seenAccountIndexes = new Map() for (const account of syncedAccounts) { if (!account) { continue } 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 } seenAccountIndexes.set(identityKey, dedupedAccounts.length) dedupedAccounts.push(account) } state.accounts = dedupedAccounts if (duplicateAccounts.length > 0) { void cleanupDuplicateDesktopAccounts(duplicateAccounts) } if (source === 'startup') { recordActivity('info', '本地账号同步', `已同步 ${state.accounts.length} 个桌面账号。`) } refreshAccountNames() pumpExecutionLoop() void sendHeartbeat('timer') } catch (error) { const message = errorMessage(error) state.lastError = message setSchedulerError(message) recordActivity('warn', '账号同步失败', message) } } async function cleanupDuplicateDesktopAccounts(accounts: DesktopAccountInfo[]): Promise { 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, platform: account.platform, platformUid: account.platform_uid, message: errorMessage(error), }) } } } function buildAccountHealthReport(account: DesktopAccountInfo): DesktopAccountHealthReport { const projected = getProjectedAccountHealth({ accountId: account.id, platform: account.platform, health: account.health, verifiedAt: account.verified_at, }) const health = projected.authState === 'expiring_soon' && projected.probeState !== 'network_error' ? 'live' : projected.health return { account_id: account.id, platform: account.platform, platform_uid: normalizeAccountPlatformUid(account.platform_uid) || account.platform_uid, health, auth_state: projected.authState, probe_state: projected.probeState, auth_reason: projected.authReason, display_name: projected.profile?.displayName ?? account.display_name, avatar_url: projected.profile?.avatarUrl ?? account.avatar_url, verified_at: isoFromTimestamp(projected.lastVerifiedAt) ?? account.verified_at, checked_at: new Date().toISOString(), } } function isAccountHealthFailureReport(report: DesktopAccountHealthReport): boolean { return ( report.health !== 'live' || report.auth_state === 'expired' || report.auth_state === 'challenge_required' || report.auth_state === 'revoked' || report.probe_state === 'network_error' ) } function shouldQueueAccountHealthReport( account: DesktopAccountInfo, report: DesktopAccountHealthReport, ): boolean { if (isAccountHealthFailureReport(report)) { return true } if (state.accountHealthReportBadAccounts.has(account.id)) { return true } return (state.accountRemoteHealth.get(account.id) ?? account.health) !== 'live' } function enqueueAccountHealthReport(accountId: string): void { const account = state.accounts.find((item) => item.id === accountId) ?? null if (!account) { return } if (!hasLocalAccountAuthorization(account)) { return } const report = buildAccountHealthReport(account) if (!shouldQueueAccountHealthReport(account, report)) { return } if (isAccountHealthFailureReport(report)) { state.accountHealthReportBadAccounts.add(account.id) } state.accountHealthReportQueue.set(account.id, report) if (state.accountHealthReportTimer) { return } state.accountHealthReportTimer = setTimeout(() => { state.accountHealthReportTimer = null void flushAccountHealthReports() }, accountHealthReportDebounceMs) } async function flushAccountHealthReports(): Promise { if (state.accountHealthReportQueue.size === 0 || !canRunLive(state.session)) { return } const reports = [...state.accountHealthReportQueue.values()].slice( 0, accountHealthReportMaxBatchSize, ) for (const report of reports) { state.accountHealthReportQueue.delete(report.account_id) } try { await reportDesktopAccountHealth({ reports }) for (const report of reports) { state.accountRemoteHealth.set(report.account_id, report.health) if (isAccountHealthFailureReport(report)) { state.accountHealthReportBadAccounts.add(report.account_id) } else { state.accountHealthReportBadAccounts.delete(report.account_id) } } } catch (error) { console.warn('[desktop-runtime] account health report failed', { count: reports.length, message: errorMessage(error), }) for (const report of reports) { state.accountHealthReportQueue.set(report.account_id, report) } } finally { if (state.accountHealthReportQueue.size > 0 && !state.accountHealthReportTimer) { state.accountHealthReportTimer = setTimeout(() => { state.accountHealthReportTimer = null void flushAccountHealthReports() }, accountHealthReportDebounceMs) } } } async function mirrorProjectedAccountProfile(accountId: string): Promise { const account = state.accounts.find((item) => item.id === accountId) ?? null if (!account) { return } const projected = getProjectedAccountHealth({ accountId: account.id, platform: account.platform, health: account.health, verifiedAt: account.verified_at, }) if (!isDefinitiveAuthState(projected.authState)) { return } const nextDisplayName = projected.profile?.displayName ?? account.display_name const nextAvatarUrl = projected.profile?.avatarUrl ?? account.avatar_url const shouldMirrorProfile = account.display_name !== nextDisplayName || account.avatar_url !== nextAvatarUrl if (!shouldMirrorProfile) { return } const normalizedPlatformUid = normalizeAccountPlatformUid(account.platform_uid) || account.platform_uid const updated = await upsertDesktopAccount({ platform: account.platform, platform_uid: normalizedPlatformUid, display_name: nextDisplayName, avatar_url: nextAvatarUrl, account_fingerprint: account.account_fingerprint ?? normalizedPlatformUid, health: account.health, verified_at: account.verified_at, tags: account.tags, }) state.accounts = state.accounts.map((item) => item.id === account.id ? { ...updated, platform_uid: normalizeAccountPlatformUid(updated.platform_uid) || updated.platform_uid, health: account.health, } : item, ) state.accountProfiles.set(account.id, { platformUid: normalizedPlatformUid, displayName: nextDisplayName, avatarUrl: nextAvatarUrl, }) refreshAccountNames() } export async function refreshRuntimeAccounts(): Promise { await syncAccounts('manual') } export async function requestRuntimeAccountProbe( accountId: string, options: { account?: PublishAccountIdentity; force?: boolean } = {}, ): Promise { const existingAccount = state.accounts.find((item) => item.id === accountId) ?? null const account = options.account ?? (existingAccount ? accountIdentityFromDesktopAccount(existingAccount) : null) if (!account) { throw new Error('runtime_account_not_found') } if (existingAccount && !hasLocalAccountAuthorization(existingAccount)) { markTrackedAccountMissingPartition(account) return } try { await probeTrackedAccount(account, { trigger: 'manual', allowSilentRefresh: true, force: options.force ?? true, }) enqueueAccountHealthReport(account.id) await mirrorProjectedAccountProfile(account.id) } catch (error) { console.warn('[desktop-runtime] account probe failed', { accountId: account.id, platform: account.platform, message: errorMessage(error), }) throw error } } export function noteRuntimeAccountBound(account: DesktopAccountInfo): void { const verifiedAt = new Date().toISOString() const normalizedAccount: DesktopAccountInfo = { ...account, platform_uid: normalizeAccountPlatformUid(account.platform_uid) || account.platform_uid, health: 'live', verified_at: account.verified_at ?? verifiedAt, } 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) => index === existingIndex ? normalizedAccount : item, ) } else { state.accounts = [normalizedAccount, ...state.accounts] } state.accountProfiles.set(normalizedAccount.id, { platformUid: normalizedAccount.platform_uid, 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 .filter((item) => hasLocalAccountAuthorization(item)) .map((item) => accountIdentityFromDesktopAccount(item)), ) markTrackedAccountBound(accountIdentityFromDesktopAccount(normalizedAccount), { subjectUid: normalizedAccount.platform_uid, displayName: normalizedAccount.display_name, avatarUrl: normalizedAccount.avatar_url, }) enqueueAccountHealthReport(normalizedAccount.id) refreshAccountNames() } export async function unbindRuntimeAccount(accountId: string, syncVersion: number): Promise { const matchedAccount = state.accounts.find((item) => item.id === accountId) ?? null await deleteDesktopAccount(accountId, syncVersion) await clearSessionHandle(accountId) forgetTrackedAccountHealth(accountId) state.accounts = state.accounts.filter((item) => item.id !== accountId) state.accountProfiles.delete(accountId) refreshAccountNames() if (matchedAccount) { recordActivity( 'info', '账号已解绑', `${matchedAccount.display_name} 已解绑,并清理了当前机器上的本地会话缓存。`, ) } await syncAccounts('manual') } function enqueuePublishTask(taskId: string, platform: string, routing: RuntimeTaskRouting): void { if (!taskId || state.activeExecutions.has(taskId)) { return } enqueuePublishSchedulerTask({ taskId, platform, routing }) syncSchedulerSurface() } function createActiveExecution( taskRecord: RuntimeTaskRecord, abortController: AbortController, ): ActiveRuntimeExecution { const now = Date.now() return { taskId: taskRecord.id, kind: taskRecord.kind, platform: taskRecord.platform, accountId: taskRecord.accountId, abortController, executionPhase: taskRecord.kind === 'monitor' ? 'PREPARING' : null, interruptRequested: taskRecord.kind === 'monitor' && Boolean(taskRecord.payload.interrupt_requested), interruptGeneration: taskRecord.kind === 'monitor' ? toInterruptGeneration(taskRecord.payload.interrupt_generation) : 0, interruptReason: taskRecord.kind === 'monitor' ? interruptReasonFromTask(taskRecord) : null, lastSafePointAt: taskRecord.kind === 'monitor' ? now : null, startedAt: now, lastProgressAt: now, lastProgressSummary: taskRecord.summary, deadlineAt: null, deadlineHandle: null, longRunningNotifiedAt: null, submitStartedAt: null, } } function armPublishTaskDeadline( taskRecord: RuntimeTaskRecord, active: ActiveRuntimeExecution, ): void { if (taskRecord.kind !== 'publish') { return } const taskDeadlineMs = resolvePublishTaskTimeoutMs(taskRecord.platform) active.deadlineAt = active.startedAt + taskDeadlineMs active.deadlineHandle = setTimeout(() => { if (active.abortController.signal.aborted) { return } const timeoutError = new Error('publish_task_timeout') recordActivity( 'warn', '任务超时', `${taskRecord.title} 执行超过 ${Math.round(taskDeadlineMs / 1000)}s,已中止并释放。`, ) updateTaskProgress(taskRecord.id, `${taskRecord.title} 执行超时,正在释放任务。`) active.abortController.abort(timeoutError) }, taskDeadlineMs) } function resolvePublishTaskTimeoutMs(platform: string): number { const envKey = `GEO_DESKTOP_PUBLISH_TIMEOUT_${platform.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_MS` return ( resolvePositiveEnvInteger(envKey) ?? resolvePositiveEnvInteger('GEO_DESKTOP_PUBLISH_TIMEOUT_MS') ?? defaultPublishTaskTimeoutMs ) } function resolvePositiveEnvInteger(key: string): number | null { const raw = process.env[key]?.trim() if (!raw) { return null } const parsed = Number.parseInt(raw, 10) return Number.isFinite(parsed) && parsed > 0 ? parsed : null } // Stages emitted by an adapter right before it performs an irreversible platform write (creating // the article/draft). After this point the article may exist on the platform regardless of whether // the local task records it, so an interrupted publish must not be auto-retried. function isIrreversiblePublishStage(stage: string): boolean { const normalized = stage.trim().toLowerCase() return ( normalized.endsWith('.create_note') || normalized.endsWith('.create_article_id') || normalized.endsWith('.create_draft') || normalized.endsWith('.publish') || normalized.endsWith('.publish_draft') || normalized.endsWith('.save_content') || normalized.endsWith('.submit') || normalized.endsWith('.submit_editor') || normalized.endsWith('.save_draft') || normalized === 'create_note' || normalized === 'create_article_id' || normalized === 'create_draft' || normalized === 'publish' || normalized === 'publish_draft' || normalized === 'save_content' || normalized === 'submit' || normalized === 'submit_editor' || normalized === 'save_draft' ) } // Persist a durable "submit started" marker before the irreversible platform write. The local flag // is set only after the server confirms the marker, so recovery can trust publish_submit_started_at. async function markPublishSubmitStartedIfNeeded(taskId: string, stage: string): Promise { if (!isIrreversiblePublishStage(stage)) { return } const active = state.activeExecutions.get(taskId) if (!active || active.kind !== 'publish' || active.submitStartedAt !== null) { return } const leaseToken = state.tasks.get(taskId)?.leaseToken if (!leaseToken) { throw new Error('publish_submit_marker_lease_missing') } await markDesktopTaskPublishSubmitStarted(taskId, leaseToken) active.submitStartedAt = Date.now() state.activeExecutions.set(taskId, active) } function asSubmitUncertainExecution(result: AdapterExecutionResult): AdapterExecutionResult { if (result.status !== 'failed') { return result } return { ...result, status: 'unknown', summary: '发布可能已提交到平台,结果待人工确认,已避免自动重复发布。', error: { ...(result.error ?? {}), publish_submit_uncertain: true, original_status: 'failed', original_summary: result.summary, }, } } function pumpExecutionLoop(): void { if (!state.running) { return } syncSchedulerSurface() if (canStartAnotherPublishExecution()) { const selected = selectNextPublishTask({ maxConcurrency: resolveAdaptiveTotalConcurrency(), activePlatforms: activePublishPlatforms(), activeCount: activePublishCount(), }) if (selected) { void leaseSpecificTask(selected, 'publish') return } } if (shouldPullPublishFallback()) { void pullNextTask('publish') return } if (hasPublishBacklog()) { return } if (canStartAnotherMonitorExecution()) { const selected = selectNextMonitorTask({ maxConcurrency: resolveAdaptiveMonitorConcurrency(), activePlatforms: activeMonitorPlatforms(), activeQuestionKeys: activeMonitorQuestionKeys(), activeCount: activeMonitorCount(), }) if (selected) { void leaseSpecificTask(selected, 'monitor') return } } return } async function leaseSpecificTask( request: PendingTaskRequest, expectedKind: 'publish' | 'monitor', ): Promise { if (!canRunLive(state.session) || isLeaseInFlight(expectedKind)) { return } state.leaseInFlightKinds.add(expectedKind) try { if (expectedKind === 'monitor' && request.source === 'monitoring_lease') { state.leaseInFlightKinds.delete(expectedKind) await executeLeasedMonitoringTask(request.taskId, request.routing) return } const leased = await leaseDesktopTask({ task_id: request.taskId }) if (!leased.task) { if (expectedKind === 'monitor') { noteMonitorTaskLeaseMiss(request.taskId) } else { notePublishTaskLeaseMiss(request.taskId) } return } state.leaseInFlightKinds.delete(expectedKind) await executeLeasedTask(leased, request.routing) } catch (error) { if (expectedKind === 'monitor') { if (error instanceof ApiClientError && error.code === 40991) { noteMonitorTaskCanceled(request.taskId) const existing = state.tasks.get(request.taskId) if (existing) { existing.status = 'aborted' existing.leaseToken = null existing.summary = '本地缓存的监控任务已不在服务端 queued,已从调度队列移除。' existing.updatedAt = Date.now() state.tasks.set(request.taskId, existing) } } else { noteMonitorTaskLeaseMiss(request.taskId) } } else { notePublishTaskLeaseMiss(request.taskId) } state.lastError = errorMessage(error) setSchedulerError(state.lastError) recordActivity('warn', '指定任务领取失败', `${request.taskId} 未能成功领取:${state.lastError}`) } finally { if (state.leaseInFlightKinds.has(expectedKind)) { state.leaseInFlightKinds.delete(expectedKind) syncSchedulerSurface() if (localQueueDepth() > 0) { pumpExecutionLoop() } } } } async function pullNextTask(kind: 'publish' | 'monitor'): Promise { if (kind === 'monitor') { if (!canRunLive(state.session) || isLeaseInFlight('monitor')) { return } state.leaseInFlightKinds.add('monitor') try { const leased = await leaseDesktopTask({ kind: 'monitor' }) state.lastPullAt = Date.now() state.lastPullStatus = 'success' noteTransportPull(true) noteSchedulerPull() if (leased.task) { state.leaseInFlightKinds.delete('monitor') await executeLeasedTask(leased, 'rabbitmq-primary') return } } catch (error) { state.lastPullAt = Date.now() state.lastPullStatus = 'failed' state.lastError = errorMessage(error) noteTransportPull(false) setSchedulerError(state.lastError) } finally { state.leaseInFlightKinds.delete('monitor') syncSchedulerSurface() } await pullMonitoringTasks('db-recovery') return } if (!canRunLive(state.session) || isLeaseInFlight('publish')) { return } let leasedPublishTask = false state.leaseInFlightKinds.add('publish') try { const leased = await leaseDesktopTask({ kind }) state.lastPullAt = Date.now() state.lastPullStatus = 'success' noteTransportPull(true) noteSchedulerPull() if (!leased.task) { state.publishFallbackBackoffUntil = Date.now() + pullIntervalMs return } state.publishFallbackBackoffUntil = 0 leasedPublishTask = true state.leaseInFlightKinds.delete('publish') await executeLeasedTask(leased, 'db-recovery') } catch (error) { state.lastPullAt = Date.now() state.lastPullStatus = 'failed' state.lastError = errorMessage(error) state.publishFallbackBackoffUntil = Date.now() + pullIntervalMs noteTransportPull(false) setSchedulerError(state.lastError) recordActivity('warn', '兜底拉取失败', state.lastError) } finally { state.leaseInFlightKinds.delete('publish') syncSchedulerSurface() if (!leasedPublishTask && state.running) { queueMicrotask(() => pumpExecutionLoop()) } } } async function pullMonitoringTasks(routing: RuntimeTaskRouting): Promise { const leaseLimit = resolveLegacyMonitoringLeaseLimit() if (!canRunLive(state.session) || isLeaseInFlight('monitor') || leaseLimit <= 0) { return } state.leaseInFlightKinds.add('monitor') try { const leased = await leaseMonitoringTasks({ limit: leaseLimit, }) state.lastPullAt = Date.now() state.lastPullStatus = 'success' noteTransportPull(true) noteSchedulerPull() enqueueLeasedMonitoringTasks(leased.tasks, routing) } catch (error) { state.lastPullAt = Date.now() state.lastPullStatus = 'failed' state.lastError = errorMessage(error) noteTransportPull(false) setSchedulerError(state.lastError) recordActivity('warn', '监控任务拉取失败', state.lastError) } finally { state.leaseInFlightKinds.delete('monitor') syncSchedulerSurface() } } async function resumeLeasedMonitoringTasks(source: 'startup' | 'reconnect'): Promise { if (!canRunLive(state.session)) { return } try { const resumed = await resumeMonitoringTasks({}) enqueueLeasedMonitoringTasks(resumed.tasks, 'db-recovery') if (resumed.tasks.length > 0) { recordActivity( 'info', '已恢复监控租约', `从服务端恢复了 ${resumed.tasks.length} 条未完成监控任务(${source})。`, ) } void pullNextTask('monitor') } catch (error) { recordActivity('warn', '恢复监控租约失败', errorMessage(error)) } } function enqueueLeasedMonitoringTasks( tasks: MonitoringLeaseTask[], routing: RuntimeTaskRouting, ): void { if (tasks.length === 0) { return } for (const task of tasks) { const taskId = String(task.task_id) const accountId = resolveMonitoringAccountID(task.ai_platform_id) const now = Date.now() state.tasks.set(taskId, { id: taskId, jobId: taskId, title: buildMonitoringTaskTitle(task), kind: 'monitor', platform: task.ai_platform_id, accountId, accountName: accountId ? resolveAccountName(accountId, task.platform_name) : task.platform_name, clientId: state.client?.id ?? taskId, status: 'queued', routing, leaseExpiresAt: parseTimestamp(task.lease_expires_at), startedAt: null, updatedAt: now, summary: `已领取监控租约,等待本地调度执行(${describeRouting(routing)})。`, payload: { title: buildMonitoringTaskTitle(task), question_id: task.question_id, question_hash: task.question_hash, question_text: task.question_text, ai_platform_id: task.ai_platform_id, platform_name: task.platform_name, collector_type: task.collector_type, run_mode: task.run_mode, trigger_source: task.trigger_source, business_date: task.business_date, priority: typeof task.priority === 'number' ? task.priority : null, lane: typeof task.lane === 'string' ? task.lane : null, monitoring_task_id: task.task_id, lease_token: task.lease_token, lease_expires_at: task.lease_expires_at, scheduler_group_key: task.question_hash || `qid:${task.question_id}`, legacy_monitoring_task: true, }, error: null, result: null, attemptId: null, leaseToken: task.lease_token, }) enqueueMonitorLeaseTask({ taskId, jobId: taskId, accountId, clientId: state.client?.id ?? taskId, platform: task.ai_platform_id, routing, priority: typeof task.priority === 'number' ? task.priority : task.trigger_source === 'manual' ? 5000 : 100, lane: typeof task.lane === 'string' ? task.lane : task.trigger_source === 'manual' ? 'high' : 'normal', title: buildMonitoringTaskTitle(task), businessDate: task.business_date, questionKey: task.question_hash || `qid:${task.question_id}`, questionText: task.question_text, }) } syncSchedulerSurface() pumpExecutionLoop() } async function executeLeasedMonitoringTask( taskId: string, _routing: RuntimeTaskRouting, ): Promise { const taskRecord = state.tasks.get(taskId) if (!taskRecord || taskRecord.kind !== 'monitor' || !taskRecord.leaseToken) { noteMonitorTaskLeaseMiss(taskId) return } const monitoringTaskID = Number.parseInt(taskId, 10) if (!Number.isFinite(monitoringTaskID)) { noteMonitorTaskCanceled(taskId) state.tasks.delete(taskId) return } setSchedulerPhase('running') setActiveLease({ taskId, attemptId: null, leaseExpiresAt: isoFromTimestamp(taskRecord.leaseExpiresAt), }) recordActivity('info', '监控任务开始执行', `${taskRecord.title} 已进入本地执行队列。`) const abortController = new AbortController() state.activeExecutions.set(taskRecord.id, createActiveExecution(taskRecord, abortController)) noteMonitorTaskActivated(taskId) syncSchedulerSurface() queueMicrotask(() => pumpExecutionLoop()) try { const execution = (await resolveAuthorizationPreflightBlock(taskRecord)) ?? withAuthorizationRetryPolicy( taskRecord, await executeTaskAdapter(taskRecord, abortController.signal), ) if (execution.status === 'unknown') { const skipReason = monitoringSkipReasonFromExecution(execution) await skipMonitoringTask(monitoringTaskID, { lease_token: taskRecord.leaseToken, skip_reason: skipReason, error_message: execution.summary, completed_at: new Date().toISOString(), }) const existing = state.tasks.get(taskId) if (existing) { existing.status = 'aborted' existing.summary = execution.summary existing.error = execution.error ?? null existing.updatedAt = Date.now() existing.leaseToken = null state.tasks.set(taskId, existing) } noteLeaseReleased('completed', taskId) noteMonitorTaskCompleted(taskId) recordActivity( skipReason === 'stale_business_date' ? 'info' : 'warn', skipReason === 'stale_business_date' ? '监控任务已按跨日策略丢弃' : '监控任务结果待确认', `${taskRecord.title} ${execution.summary}`, ) return } await submitMonitoringTaskResult( monitoringTaskID, buildMonitoringResultPayload(taskRecord.leaseToken, execution), ) const existing = state.tasks.get(taskId) if (existing) { existing.status = execution.status existing.summary = execution.summary existing.result = execution.payload ?? null existing.error = execution.error ?? null existing.updatedAt = Date.now() existing.leaseToken = null state.tasks.set(taskId, existing) } noteLeaseReleased(execution.status === 'failed' ? 'failed' : 'completed', taskId) noteMonitorTaskCompleted(taskId) recordActivity( execution.status === 'failed' ? 'danger' : 'success', execution.status === 'failed' ? '监控任务执行失败' : '监控任务执行成功', `${taskRecord.title} ${execution.summary}`, ) } catch (error) { if (isMonitorAbortReason(taskRecord, error, 'stale_business_date')) { let dropSummary = staleMonitoringDropSummary(taskRecord) try { await skipMonitoringTask(monitoringTaskID, { lease_token: taskRecord.leaseToken, skip_reason: 'stale_business_date', error_message: dropSummary, completed_at: new Date().toISOString(), }) } catch (skipError) { dropSummary = `${dropSummary}(skip 回写失败:${errorMessage(skipError)})` } const existing = state.tasks.get(taskId) if (existing) { existing.status = 'aborted' existing.summary = dropSummary existing.error = { ...toStructuredError(error), dropped_reason: 'stale_business_date', } existing.updatedAt = Date.now() existing.leaseToken = null state.tasks.set(taskId, existing) } noteLeaseReleased('completed', taskId) noteMonitorTaskCompleted(taskId) recordActivity('info', '监控任务已按跨日策略丢弃', dropSummary) return } const message = errorMessage(error) const existing = state.tasks.get(taskId) if (existing) { existing.status = 'failed' existing.summary = message existing.error = toStructuredError(error) existing.updatedAt = Date.now() existing.leaseToken = null state.tasks.set(taskId, existing) } noteLeaseReleased('failed', taskId) noteMonitorTaskCompleted(taskId) recordActivity('danger', '监控任务执行失败', `${taskRecord.title} 执行失败:${message}`) } finally { state.activeExecutions.delete(taskId) syncSchedulerSurface() noteSchedulerTaskFinished() pumpExecutionLoop() } } function buildMonitoringTaskTitle(task: MonitoringLeaseTask): string { const base = task.question_text.trim() if (!base) { return `监控任务 · ${task.platform_name}` } return `${task.platform_name} · ${base}` } function resolveMonitoringAccountID(platform: string): string { const live = state.accounts.find( (account) => account.platform === platform && getProjectedAccountHealth({ accountId: account.id, platform: account.platform, health: account.health, verifiedAt: account.verified_at, }).health === 'live', ) if (live) { return live.id } return state.accounts.find((account) => account.platform === platform)?.id ?? '' } function buildMonitoringResultPayload( leaseToken: string, execution: AdapterExecutionResult, ): MonitoringTaskResultPayload { const payload = execution.payload ?? {} const status = execution.status === 'succeeded' ? 'succeeded' : 'failed' const rawResponseJSON = asRecord(payload.raw_response_json) const failedRawResponseJSON = status === 'failed' && execution.error ? { ...rawResponseJSON, runtime_error: execution.error, } : rawResponseJSON return { lease_token: leaseToken, status, provider_model: asOptionalString(payload.provider_model), provider_request_id: asOptionalString(payload.provider_request_id), request_id: asOptionalString(payload.request_id), answer: asOptionalString(payload.answer), raw_response_json: failedRawResponseJSON, citations: asSourceItems(payload.citations), search_results: asSourceItems(payload.search_results), brand_mentioned: asOptionalBoolean(payload.brand_mentioned), brand_mention_position: asOptionalString(payload.brand_mention_position), first_recommended: asOptionalBoolean(payload.first_recommended), sentiment_label: asOptionalString(payload.sentiment_label), matched_brand_terms: asStringArray(payload.matched_brand_terms), error_message: status === 'failed' ? execution.summary : undefined, completed_at: new Date().toISOString(), } } function buildMonitoringFailurePayload( leaseToken: string, failureMessage: string, structuredError: Record, task?: RuntimeTaskRecord, ): MonitoringTaskResultPayload { const runtimeError = task ? (withAuthorizationRetryPolicy(task, { status: 'failed', summary: failureMessage, error: structuredError, }).error ?? structuredError) : structuredError return { lease_token: leaseToken, status: 'failed', raw_response_json: { runtime_error: runtimeError, }, error_message: failureMessage, completed_at: new Date().toISOString(), } } async function executeLeasedDesktopMonitorTask( taskRecord: RuntimeTaskRecord, leased: LeaseDesktopTaskResponse, routing: RuntimeTaskRouting, signal: AbortSignal, ): Promise { const monitoringTaskID = extractMonitoringTaskID(taskRecord.payload) if (!monitoringTaskID) { const missingMessage = 'monitor desktop task is missing monitoring_task_id' const completed = await completeDesktopTask(taskRecord.id, { lease_token: leased.lease_token as string, status: 'failed', error: { code: 'monitoring_task_id_missing', message: missingMessage, }, }) upsertTaskFromInfo(completed, { routing, summary: missingMessage, attemptId: null, leaseToken: null, }) noteLeaseReleased('failed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity( 'danger', '监控任务执行失败', `${taskRecord.title} 缺少 monitoring_task_id,无法回写结果。`, ) return } try { const execution = (await resolveAuthorizationPreflightBlock(taskRecord)) ?? withAuthorizationRetryPolicy(taskRecord, await executeTaskAdapter(taskRecord, signal)) if (execution.status === 'unknown') { const skipReason = monitoringSkipReasonFromExecution(execution) await skipMonitoringTask(monitoringTaskID, { lease_token: leased.lease_token as string, skip_reason: skipReason, error_message: execution.summary, completed_at: new Date().toISOString(), }) if (skipReason === 'stale_business_date') { const canceled = await cancelDesktopTask(taskRecord.id, { lease_token: leased.lease_token as string, reason: skipReason, }).catch((cancelError) => { const existing = state.tasks.get(taskRecord.id) if (existing) { existing.status = 'aborted' existing.error = { ...(execution.error ?? {}), cancel_error: errorMessage(cancelError), dropped_reason: 'stale_business_date', } existing.summary = `${execution.summary}(desktop task 取消失败:${errorMessage(cancelError)})` existing.updatedAt = Date.now() existing.leaseToken = null state.tasks.set(taskRecord.id, existing) } return null }) if (canceled) { upsertTaskFromInfo(canceled, { routing, summary: execution.summary, attemptId: null, leaseToken: null, }) } noteLeaseReleased('completed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity( 'info', '监控任务已按跨日策略丢弃', `${taskRecord.title} ${execution.summary}`, ) return } const completed = await completeDesktopTask(taskRecord.id, { lease_token: leased.lease_token as string, status: 'unknown', error: execution.error ?? { code: 'runtime_unknown', message: execution.summary, }, }) upsertTaskFromInfo(completed, { routing, summary: execution.summary, attemptId: null, leaseToken: null, }) noteLeaseReleased('completed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity('warn', '监控任务结果待确认', `${taskRecord.title} ${execution.summary}`) return } await submitMonitoringTaskResult( monitoringTaskID, buildMonitoringResultPayload(leased.lease_token as string, execution), ) const existing = state.tasks.get(taskRecord.id) if (existing) { existing.status = execution.status existing.summary = execution.summary existing.result = execution.payload ?? null existing.error = execution.error ?? null existing.updatedAt = Date.now() existing.attemptId = null existing.leaseToken = null state.tasks.set(taskRecord.id, existing) } noteLeaseReleased(execution.status === 'failed' ? 'failed' : 'completed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity( execution.status === 'failed' ? 'danger' : 'success', execution.status === 'failed' ? '监控任务执行失败' : '监控任务执行成功', `${taskRecord.title} ${execution.summary}`, ) } catch (error) { if (isMonitorAbortReason(taskRecord, error, 'stale_business_date')) { let dropSummary = staleMonitoringDropSummary(taskRecord) try { await skipMonitoringTask(monitoringTaskID, { lease_token: leased.lease_token as string, skip_reason: 'stale_business_date', error_message: dropSummary, completed_at: new Date().toISOString(), }) } catch (skipError) { dropSummary = `${dropSummary}(monitoring skip 回写失败:${errorMessage(skipError)})` } const canceled = await cancelDesktopTask(taskRecord.id, { lease_token: leased.lease_token as string, reason: 'stale_business_date', }).catch((cancelError) => { const existing = state.tasks.get(taskRecord.id) if (existing) { existing.status = 'aborted' existing.error = { ...toStructuredError(error), cancel_error: errorMessage(cancelError), dropped_reason: 'stale_business_date', } existing.summary = `${dropSummary}(desktop task 取消失败:${errorMessage(cancelError)})` existing.updatedAt = Date.now() existing.leaseToken = null state.tasks.set(taskRecord.id, existing) } return null }) if (canceled) { upsertTaskFromInfo(canceled, { routing, summary: dropSummary, attemptId: null, leaseToken: null, }) } noteLeaseReleased('completed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity('info', '监控任务已按跨日策略丢弃', dropSummary) return } if (isDesktopTaskMonitorInterrupt(taskRecord, error)) { const canceled = await cancelDesktopTask(taskRecord.id, { lease_token: leased.lease_token as string, reason: interruptReasonFromTask(taskRecord), }) upsertTaskFromInfo(canceled, { routing, summary: '已响应 Phase 2 抢占请求,当前监控任务已回队等待 retry。', attemptId: null, leaseToken: null, }) noteLeaseReleased('completed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity('warn', '监控任务已让路', `${taskRecord.title} 已安全中止,并回队为 retry。`) return } const failureMessage = errorMessage(error) const structuredError = toStructuredError(error) let monitoringResultWriteError: unknown = null try { await submitMonitoringTaskResult( monitoringTaskID, buildMonitoringFailurePayload( leased.lease_token as string, failureMessage || '监控任务执行失败。', structuredError, taskRecord, ), ) } catch (submitError) { monitoringResultWriteError = submitError } const failureSummary = monitoringResultWriteError ? `${failureMessage || '监控任务执行失败。'}(monitoring 失败回传失败:${errorMessage(monitoringResultWriteError)})` : failureMessage || '监控任务执行失败。' const existing = state.tasks.get(taskRecord.id) if (existing) { existing.status = 'failed' existing.error = structuredError existing.summary = failureSummary existing.updatedAt = Date.now() existing.attemptId = null existing.leaseToken = null state.tasks.set(taskRecord.id, existing) } noteLeaseReleased('failed', taskRecord.id) noteMonitorTaskCompleted(taskRecord.id) recordActivity( 'danger', '监控任务执行失败', `${taskRecord.title} 执行失败:${failureSummary || '未知错误'}`, ) } } function extractMonitoringTaskID(payload: Record): number | null { const raw = payload.monitoring_task_id if (typeof raw === 'number' && Number.isFinite(raw)) { return raw } if (typeof raw === 'string' && raw.trim()) { const parsed = Number.parseInt(raw.trim(), 10) if (Number.isFinite(parsed)) { return parsed } } return null } function noteMonitorExecutionSafePoint(taskId: string, phase: MonitorExecutionPhase | null): void { const active = state.activeExecutions.get(taskId) if (!active || active.kind !== 'monitor') { return } if (phase) { active.executionPhase = phase } active.lastSafePointAt = Date.now() active.lastProgressAt = Date.now() state.activeExecutions.set(taskId, active) const taskRecord = state.tasks.get(taskId) if (taskRecord?.kind === 'monitor') { const staleBusinessDate = resolveStaleMonitoringBusinessDate(taskRecord.payload) if (staleBusinessDate) { requestMonitorStaleBusinessDateDrop(taskId, staleBusinessDate) return } } if (active.interruptRequested && !active.abortController.signal.aborted) { active.abortController.abort() } } function requestMonitorStaleBusinessDateDrop(taskId: string, staleBusinessDate: string): void { const active = state.activeExecutions.get(taskId) if (!active || active.kind !== 'monitor') { return } active.interruptReason = 'stale_business_date' active.lastSafePointAt = Date.now() active.lastProgressAt = Date.now() state.activeExecutions.set(taskId, active) const existing = state.tasks.get(taskId) if (existing) { existing.payload = { ...existing.payload, interrupt_reason: 'stale_business_date', dropped_reason: 'stale_business_date', } existing.summary = staleMonitoringDropSummary(existing, staleBusinessDate) existing.updatedAt = Date.now() state.tasks.set(taskId, existing) } if (!active.abortController.signal.aborted) { active.abortController.abort() } } function monitorExecutionPhaseFromStage(stage: string): MonitorExecutionPhase { const normalized = stage.trim().toLowerCase() if (!normalized) { return 'WAITING' } if (normalized.includes('bootstrap') || normalized.includes('page_ready')) { return 'NAVIGATING' } if (normalized.includes('login') || normalized.includes('runtime_state')) { return 'AUTHENTICATING' } if (normalized.includes('input')) { return 'INPUTTING' } if (normalized.includes('query') || normalized.includes('submit')) { return 'SUBMITTING' } if (normalized.includes('parse')) { return 'PARSING' } return 'WAITING' } function isDesktopTaskMonitorInterrupt(taskRecord: RuntimeTaskRecord, error: unknown): boolean { return ( isDesktopTaskID(taskRecord.id) && isMonitorAbortReason(taskRecord, error, 'collect_now_preempt') ) } function isMonitorAbortReason( taskRecord: RuntimeTaskRecord, error: unknown, reason: string, ): boolean { return ( errorMessage(error).includes('adapter_aborted') && monitorAbortReason(taskRecord) === reason ) } function monitorAbortReason(taskRecord: RuntimeTaskRecord): string | null { const active = state.activeExecutions.get(taskRecord.id) if (active?.interruptReason?.trim()) { return active.interruptReason.trim() } const raw = taskRecord.payload.interrupt_reason if (typeof raw === 'string' && raw.trim()) { return raw.trim() } if (Boolean(taskRecord.payload.interrupt_requested) || Boolean(active?.interruptRequested)) { return 'collect_now_preempt' } return null } function interruptReasonFromTask(taskRecord: RuntimeTaskRecord): string { return monitorAbortReason(taskRecord) ?? 'collect_now_preempt' } function isDesktopTaskID(taskId: string): boolean { return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( taskId.trim(), ) } async function executeLeasedTask( leased: LeaseDesktopTaskResponse, routing: RuntimeTaskRouting, ): Promise { if (!leased.task || !leased.lease_token) { return } const task = leased.task setSchedulerPhase('running') setActiveLease({ taskId: task.id, attemptId: leased.attempt_id ?? null, leaseExpiresAt: leased.lease_expires_at ?? task.lease_expires_at ?? null, }) const taskRecord = upsertTaskFromInfo(task, { routing, attemptId: leased.attempt_id ?? null, leaseToken: leased.lease_token, summary: `已领取租约,开始执行 ${routing === 'rabbitmq-primary' ? 'MQ 唤醒' : '兜底拉取'} 消费。`, }) recordActivity( 'info', '任务已领取', `${taskRecord.title} 已由当前客户端领取,路由:${describeRouting(routing)}。`, ) const abortController = new AbortController() const activeExecution = createActiveExecution(taskRecord, abortController) armPublishTaskDeadline(taskRecord, activeExecution) state.activeExecutions.set(taskRecord.id, activeExecution) if (task.kind === 'monitor') { noteMonitorTaskLeased(task, routing) } else { notePublishTaskActivated(taskRecord.id, taskRecord.platform) emitRuntimeInvalidated('publish-task-lease', { taskId: taskRecord.id }) } syncSchedulerSurface() queueMicrotask(() => pumpExecutionLoop()) const extendHandle = setInterval(() => { void extendActiveLease(taskRecord.id, leased.lease_token as string) }, leaseExtendIntervalMs) try { if (task.kind === 'monitor') { await executeLeasedDesktopMonitorTask(taskRecord, leased, routing, abortController.signal) return } const execution = (await resolveAuthorizationPreflightBlock(taskRecord)) ?? withAuthorizationRetryPolicy( taskRecord, await executeTaskAdapter(taskRecord, abortController.signal), ) const completed = await completeDesktopTask(taskRecord.id, { lease_token: leased.lease_token, status: execution.status, payload: execution.payload, error: execution.error, }) noteLeaseReleased(execution.status === 'failed' ? 'failed' : 'completed', taskRecord.id) upsertTaskFromInfo(completed, { routing, summary: execution.summary, attemptId: null, leaseToken: null, }) recordActivity( execution.status === 'failed' ? 'danger' : execution.status === 'unknown' ? 'warn' : 'success', execution.status === 'failed' ? '任务执行失败' : execution.status === 'unknown' ? '任务执行结果待确认' : '任务执行成功', `${taskRecord.title} ${execution.summary}`, ) } catch (error) { const failureMessage = errorMessage(error) const structuredError = toStructuredError(error) const accountIdentity = accountIdentityFromTask(taskRecord) // If a publish task was interrupted AFTER it may have entered the irreversible submit phase, // the article might already be live on the platform. Completing as a clean 'failed' would drop // it from the server dedup set and let a retry double-post. Complete as 'unknown' instead so it // stays for manual reconcile. (A pre-submit interruption is a true failure and stays 'failed'.) const submitMaybeStarted = taskRecord.kind === 'publish' && activeExecution.submitStartedAt !== null const terminalStatus: 'failed' | 'unknown' = submitMaybeStarted ? 'unknown' : 'failed' const interruptedSummary = '发布可能已提交到平台,结果待人工确认,已避免自动重复发布。' // Tag the persisted error so the desktop UI can require explicit confirmation before a manual // retry of a possibly-already-published task (mirrors the server recovery payload marker). const terminalError = submitMaybeStarted ? { ...structuredError, publish_submit_uncertain: true } : structuredError // A submit-phase interruption is typically a network/timeout stall, not an auth problem — don't // wrongly degrade account health for it. if (accountIdentity && !submitMaybeStarted) { await reportAccountFailure(accountIdentity, { summary: failureMessage, error: structuredError, }).catch((reportError) => { console.warn('[desktop-runtime] thrown failure report failed', { taskId: taskRecord.id, accountId: accountIdentity.id, platform: accountIdentity.platform, message: reportError instanceof Error ? reportError.message : String(reportError), }) }) } try { const completed = await completeDesktopTask(taskRecord.id, { lease_token: leased.lease_token, status: terminalStatus, error: terminalError, }) upsertTaskFromInfo(completed, { routing, summary: submitMaybeStarted ? interruptedSummary : failureMessage || '桌面任务执行失败。', attemptId: null, leaseToken: null, }) } catch (completeError) { const existing = state.tasks.get(taskRecord.id) if (existing) { existing.status = terminalStatus existing.error = terminalError existing.summary = submitMaybeStarted ? `${interruptedSummary}(result 回写失败:${errorMessage(completeError)})` : `${failureMessage || '桌面任务执行失败。'}(result 回写失败:${errorMessage(completeError)})` existing.updatedAt = Date.now() state.tasks.set(taskRecord.id, existing) } } noteLeaseReleased('failed', taskRecord.id) recordActivity( submitMaybeStarted ? 'warn' : 'danger', submitMaybeStarted ? '发布结果待确认' : '任务执行失败', submitMaybeStarted ? `${taskRecord.title} 进入提交阶段后中断,可能已发布,已转人工确认以避免重复发文。` : `${taskRecord.title} 执行失败:${failureMessage || '未知错误'}`, ) } finally { clearInterval(extendHandle) if (activeExecution.deadlineHandle) { clearTimeout(activeExecution.deadlineHandle) activeExecution.deadlineHandle = null } if (taskRecord.kind === 'publish') { notePublishTaskCompleted(taskRecord.id, taskRecord.platform) } state.activeExecutions.delete(taskRecord.id) syncSchedulerSurface() noteSchedulerTaskFinished() pumpExecutionLoop() } } async function extendActiveLease(taskId: string, leaseToken: string): Promise { const existing = state.tasks.get(taskId) const active = state.activeExecutions.get(taskId) if (existing?.kind === 'monitor') { const staleBusinessDate = resolveStaleMonitoringBusinessDate(existing.payload) if (staleBusinessDate) { requestMonitorStaleBusinessDateDrop(taskId, staleBusinessDate) return } } if (existing?.kind === 'publish' && active) { const idleMs = Date.now() - active.lastProgressAt if (idleMs >= publishTaskStaleProgressMs) { const detail = active.lastProgressSummary ? `,最后进度:${active.lastProgressSummary}` : '' existing.summary = `发布任务超过 ${Math.round(idleMs / 1000)} 秒没有新进度,已中止并释放,准备由服务端重排${detail}。` existing.updatedAt = Date.now() state.tasks.set(taskId, existing) recordActivity('warn', '发布任务疑似卡住', `${existing.title} ${existing.summary}`) // Abort the local execution instead of merely letting the lease lapse: this // releases the slot immediately and, crucially, closes the window where the // server could re-queue the task while a stalled adapter is still mid-publish // (which would double-post a non-idempotent article). if (!active.abortController.signal.aborted) { active.abortController.abort(new Error('publish_stale_progress')) } return } } try { const task = await extendDesktopTask(taskId, { lease_token: leaseToken }) noteLeaseExtended(task.lease_expires_at ?? null, taskId) const existing = state.tasks.get(taskId) if (existing) { existing.leaseExpiresAt = parseTimestamp(task.lease_expires_at) ?? existing.leaseExpiresAt existing.updatedAt = Date.now() existing.summary = existing.kind === 'publish' ? (active?.lastProgressSummary ?? '租约已自动续期,发布任务仍在执行。') : '租约已自动续期,任务仍在执行。' state.tasks.set(taskId, existing) } } catch (error) { const message = errorMessage(error) state.lastError = message setSchedulerError(message) recordActivity('warn', '租约续期失败', `${taskId} 续租失败:${message}`) } } async function executeTaskAdapter( task: RuntimeTaskRecord, signal: AbortSignal, ): Promise { const accountIdentity = accountIdentityFromTask(task) if (accountIdentity && shouldEnforceActiveAccount(task)) { const readiness = await ensureAccountReady(accountIdentity) if (readiness.authState !== 'active') { return buildAccountBlockedResult(task, readiness) } } const sessionHandle = createSessionHandle(task.accountId || task.id) const payload = task.payload if (task.kind === 'publish') { const adapter = selectPublishAdapter(task.platform) if (!adapter) { return buildScaffoldResult(task, payload, 'publish adapter is not implemented yet') } const viewHandle = adapter.executionMode === 'session' || adapter.executionMode === 'playwright' ? null : retainHotView(task.accountId || task.id) const playwrightLease = adapter.executionMode === 'playwright' ? await retainHiddenPlaywrightPage({ accountId: task.accountId || task.id, session: sessionHandle.session, targetURL: resolvePlaywrightTargetURL(task.platform, payload), title: `${task.title} · Hidden Playwright`, }) : null try { const result = await adapter.publish( { taskId: task.id, accountId: task.accountId || task.id, session: sessionHandle.session, view: viewHandle?.view ?? null, playwright: playwrightLease ? { browser: playwrightLease.browser, page: playwrightLease.page, } : null, signal, phase: 'initial', article: await loadPublishArticle(task), async reportProgress(stage: string) { noteMonitorExecutionSafePoint(task.id, null) updateTaskProgress(task.id, `publish adapter progress: ${stage}`) await markPublishSubmitStartedIfNeeded(task.id, stage) }, }, payload, ) const active = state.activeExecutions.get(task.id) const normalizedResult = active?.submitStartedAt != null ? asSubmitUncertainExecution(result) : result await maybeReportTaskAuthFailure(task, normalizedResult, accountIdentity) return normalizedResult } finally { await playwrightLease?.release() if (viewHandle) { releaseHotView(viewHandle.accountId) } } } const staleBusinessDate = resolveStaleMonitoringBusinessDate(payload) if (staleBusinessDate) { return { status: 'unknown', payload: { ...payload, dropped_by_client: true, dropped_reason: 'stale_business_date', }, error: { code: 'desktop_monitor_task_stale', message: 'monitor task is stale and has been dropped by desktop client', business_date: staleBusinessDate, }, summary: `${task.title} 已过业务日 ${staleBusinessDate},按漏采策略直接丢弃。`, } } const adapter = selectMonitorAdapter(task.platform) if (!adapter) { return buildScaffoldResult(task, payload, 'monitor adapter is not implemented yet') } const viewHandle = adapter.executionMode === 'session' || adapter.executionMode === 'playwright' ? null : retainHotView(task.accountId || task.id) const playwrightLease = adapter.executionMode === 'playwright' ? await retainHiddenPlaywrightPage({ accountId: task.accountId || task.id, session: sessionHandle.session, targetURL: resolvePlaywrightTargetURL(task.platform, payload), title: `${task.title} · Hidden Playwright`, }) : null try { const result = await adapter.query( { taskId: task.id, accountId: task.accountId || task.id, session: sessionHandle.session, view: viewHandle?.view ?? null, playwright: playwrightLease ? { browser: playwrightLease.browser, page: playwrightLease.page, } : null, signal, phase: 'initial', reportProgress(stage: string) { noteMonitorExecutionSafePoint(task.id, monitorExecutionPhaseFromStage(stage)) updateTaskProgress(task.id, `monitor adapter progress: ${stage}`) }, }, payload, ) await maybeReportTaskAuthFailure(task, result, accountIdentity) return result } finally { await playwrightLease?.release() if (viewHandle) { releaseHotView(viewHandle.accountId) } } } function shouldEnforceActiveAccount(task: RuntimeTaskRecord): boolean { if (task.kind === 'publish') { return true } if (!isAIPlatformId(task.platform)) { return true } return authRequiredMonitorPlatforms.has(task.platform) } function buildAccountBlockedResult( task: RuntimeTaskRecord, readiness: Awaited>, ): AdapterExecutionResult { if (readiness.authState === 'challenge_required') { return { status: 'failed', summary: accountActionRequiredSummary(task.platform, readiness.authReason), error: markAuthorizationFailureNonRetryable( { code: readiness.authReason === 'risk_control' ? 'desktop_account_risk_control' : 'desktop_account_challenge_required', message: readiness.authReason === 'risk_control' ? 'desktop account risk control triggered' : 'desktop account challenge required', }, isAIPlatformId(task.platform) ? 'ai_platform_authorization' : 'media_account_authorization', ), } } if (readiness.authState === 'expired' || readiness.authState === 'revoked') { return { status: 'failed', summary: `${task.accountName} 登录态已失效,请重新授权后再试。`, error: markAuthorizationFailureNonRetryable( { code: 'desktop_account_auth_expired', message: 'desktop account auth expired', }, isAIPlatformId(task.platform) ? 'ai_platform_authorization' : 'media_account_authorization', ), } } return { status: 'unknown', summary: `${task.accountName} 最近校验失败,已暂缓执行任务。`, error: { code: 'desktop_account_probe_pending', message: 'desktop account readiness pending', }, } } async function maybeReportTaskAuthFailure( task: RuntimeTaskRecord, result: AdapterExecutionResult, accountIdentity: PublishAccountIdentity | null, ): Promise { if (!accountIdentity) { return } const previousSnapshot = getAccountHealthSnapshot(accountIdentity.id) const nextSnapshot = await reportAccountFailure(accountIdentity, { summary: result.summary, error: result.error ?? null, }).catch((error) => { console.warn('[desktop-runtime] account failure report failed', { taskId: task.id, accountId: accountIdentity.id, platform: accountIdentity.platform, message: error instanceof Error ? error.message : String(error), }) return null }) maybeRecordAccountAccessAlert(task, accountIdentity, previousSnapshot, nextSnapshot) if (nextSnapshot && nextSnapshot.authState !== 'active') { enqueueAccountHealthReport(accountIdentity.id) } } function resolvePlaywrightTargetURL(platform: string, payload: Record): string { const payloadURL = resolveStringField(payload, [ 'target_url', 'targetUrl', 'console_url', 'consoleUrl', 'url', ]) if (payloadURL) { return payloadURL } const aiPlatform = getAIPlatformCatalogItem(platform) if (aiPlatform?.consoleUrl) { return aiPlatform.consoleUrl } return 'about:blank' } function buildScaffoldResult( task: RuntimeTaskRecord, adapterPayload: Record, detail: string, ): AdapterExecutionResult { return { status: 'failed', payload: { ...adapterPayload, runtime_mode: 'desktop-scaffold', task_kind: task.kind, task_platform: task.platform, }, error: { code: 'desktop_task_adapter_missing', message: 'desktop runtime adapter is not implemented for this platform', detail, }, summary: `${task.title} 执行失败:当前平台的 desktop ${task.kind === 'publish' ? '发布' : '监测'}适配器尚未实现。`, } } async function loadPublishArticle(task: RuntimeTaskRecord): Promise { const articleId = resolveArticleId(task.payload) if (articleId === null) { throw new Error('desktop_publish_article_id_missing') } const article = await getDesktopArticleContent(articleId) return { ...article, title: article.title?.trim() || task.title, } } function resolveArticleId(payload: Record): number | null { const direct = toPositiveInt(payload.article_id) if (direct !== null) { return direct } const contentRef = payload.content_ref if (!contentRef || typeof contentRef !== 'object' || Array.isArray(contentRef)) { return null } const typed = contentRef as Record return toPositiveInt(typed.id ?? typed.article_id ?? null) } function toPositiveInt(value: JsonValue | null): number | null { if (typeof value === 'number' && Number.isInteger(value) && value > 0) { return value } if (typeof value === 'string' && /^\d+$/.test(value.trim())) { const parsed = Number.parseInt(value.trim(), 10) return parsed > 0 ? parsed : null } return null } function resolveStringField(payload: Record, keys: string[]): string | null { for (const key of keys) { const value = payload[key] if (typeof value !== 'string') { continue } const normalized = value.trim() if (normalized) { return normalized } } return null } function resolveStaleMonitoringBusinessDate(payload: Record): string | null { const businessDate = resolveMonitoringBusinessDate(payload) if (!businessDate) { return null } return businessDate < currentMonitoringBusinessDate() ? businessDate : null } function monitoringSkipReasonFromExecution(execution: AdapterExecutionResult): string { return isStaleMonitoringExecutionResult(execution) ? 'stale_business_date' : 'runtime_unknown' } function isStaleMonitoringExecutionResult(execution: AdapterExecutionResult): boolean { return execution.payload?.dropped_reason === 'stale_business_date' } function staleMonitoringDropSummary( task: RuntimeTaskRecord, staleBusinessDate?: string | null, ): string { const businessDate = staleBusinessDate ?? resolveMonitoringBusinessDate(task.payload) if (businessDate) { return `${task.title} 已过业务日 ${businessDate},按漏采策略直接丢弃。` } return `${task.title} 已跨业务日,按漏采策略直接丢弃。` } function resolveMonitoringBusinessDate(payload: Record): string | null { const candidates = [ payload.business_date, payload.businessDate, payload.metric_date, payload.date, ] for (const candidate of candidates) { if (typeof candidate !== 'string') { continue } const normalized = candidate.trim() if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) { return normalized } } return null } function currentMonitoringBusinessDate(now = new Date()): string { const parts = new Intl.DateTimeFormat('en-US', { timeZone: monitorBusinessTimeZone, year: 'numeric', month: '2-digit', day: '2-digit', }).formatToParts(now) const year = parts.find((part) => part.type === 'year')?.value ?? '' const month = parts.find((part) => part.type === 'month')?.value ?? '' const day = parts.find((part) => part.type === 'day')?.value ?? '' return `${year}-${month}-${day}` } function updateTaskProgress(taskId: string, summary: string): void { const existing = state.tasks.get(taskId) if (!existing) { return } existing.summary = summary existing.updatedAt = Date.now() state.tasks.set(taskId, existing) const active = state.activeExecutions.get(taskId) if (active) { active.lastProgressAt = Date.now() active.lastProgressSummary = summary state.activeExecutions.set(taskId, active) } if (existing.kind === 'publish') { emitRuntimeInvalidated('publish-task-progress', { taskId }) } } function upsertTaskFromInfo( task: DesktopTaskInfo, options: { routing?: RuntimeTaskRouting summary?: string attemptId?: string | null leaseToken?: string | null } = {}, ): RuntimeTaskRecord { const existing = state.tasks.get(task.id) const payload = normalizeJsonObject(task.payload) const record: RuntimeTaskRecord = { id: task.id, jobId: task.job_id, title: resolveTaskTitle(task, payload, existing), kind: task.kind, platform: task.platform, accountId: task.target_account_id, accountName: resolveAccountName(task.target_account_id, existing?.accountName), clientId: task.target_client_id, status: task.status, routing: options.routing ?? existing?.routing ?? 'db-recovery', leaseExpiresAt: parseTimestamp(task.lease_expires_at) ?? null, startedAt: task.status === 'in_progress' ? (existing?.startedAt ?? Date.now()) : null, updatedAt: parseTimestamp(task.updated_at) ?? Date.now(), summary: options.summary ?? existing?.summary ?? summaryFromTaskStatus(task.status), payload, error: normalizeJsonObjectOrNull(task.error), result: normalizeJsonObjectOrNull(task.result), attemptId: task.status === 'in_progress' ? (options.attemptId ?? existing?.attemptId ?? task.active_attempt_id ?? null) : null, leaseToken: task.status === 'in_progress' ? (options.leaseToken ?? existing?.leaseToken ?? null) : null, } state.tasks.set(task.id, record) return record } function refreshAccountNames(): void { for (const [taskId, task] of state.tasks.entries()) { const nextName = resolveAccountName(task.accountId, task.accountName) if (nextName === task.accountName) { continue } state.tasks.set(taskId, { ...task, accountName: nextName }) } } function recordActivity(severity: RuntimeTone, title: string, detail: string): void { const next: RuntimeControllerActivity = { id: `rt-${Date.now()}-${(state.activitySeq += 1)}`, severity, title, detail, at: Date.now(), } state.activity.unshift(next) if (state.activity.length > maxActivityItems) { state.activity.length = maxActivityItems } } function syncSchedulerSurface(): void { setSchedulerQueueDepth(localQueueDepth()) setSchedulerCurrentTask(primaryActiveTaskId()) } function notifyLongRunningPublishTasks(): void { const now = Date.now() for (const active of state.activeExecutions.values()) { if (active.kind !== 'publish' || active.longRunningNotifiedAt) { continue } if (now - active.startedAt < publishLongRunningNotificationMs) { continue } active.longRunningNotifiedAt = now state.activeExecutions.set(active.taskId, active) const task = state.tasks.get(active.taskId) if (task) { task.summary = `${task.summary || '发布任务仍在执行。'}(已执行 ${formatDurationText(now - active.startedAt)},如网络异常可取消后重试。)` task.updatedAt = now state.tasks.set(task.id, task) recordActivity( 'warn', '发布等待较久', `${task.title} 已执行 ${formatDurationText(now - active.startedAt)},可能存在网络异常。`, ) emitRuntimeInvalidated('publish-task-progress', { taskId: task.id }) } } } function formatDurationText(ms: number): string { const seconds = Math.max(0, Math.floor(ms / 1000)) const minutes = Math.floor(seconds / 60) const restSeconds = seconds % 60 if (minutes <= 0) { return `${restSeconds} 秒` } if (restSeconds === 0) { return `${minutes} 分钟` } return `${minutes} 分 ${restSeconds} 秒` } function localQueueDepth(): number { const publish = getPublishSchedulerSnapshot() const monitor = getMonitorSchedulerSnapshot() return publish.queueDepth + monitor.queuedCount + monitor.leasingCount } function primaryActiveTaskId(): string | null { const active = [...state.activeExecutions.values()] const publish = active.find((item) => item.kind === 'publish') ?? null return publish?.taskId ?? active[0]?.taskId ?? null } function isLeaseInFlight(kind: 'publish' | 'monitor'): boolean { return state.leaseInFlightKinds.has(kind) } function activePublishCount(): number { return [...state.activeExecutions.values()].filter((item) => item.kind === 'publish').length } function activePublishPlatforms(): Set { const platforms = new Set( [...state.activeExecutions.values()] .filter((item) => item.kind === 'publish') .map((item) => item.platform), ) for (const task of getPublishSchedulerSnapshot().tasks) { if (task.state === 'active' || task.state === 'leasing') { platforms.add(task.platform) } } return platforms } function activeMonitorCount(): number { return [...state.activeExecutions.values()].filter((item) => item.kind === 'monitor').length } function activeMonitorPlatforms(): Set { const platforms = new Set( [...state.activeExecutions.values()] .filter((item) => item.kind === 'monitor') .map((item) => item.platform), ) for (const task of getMonitorSchedulerSnapshot().tasks) { if (task.state === 'active' || task.state === 'leasing') { platforms.add(task.platform) } } return platforms } function activeMonitorQuestionKeys(): Set { return new Set( getMonitorSchedulerSnapshot() .tasks.filter( (task) => (task.state === 'active' || task.state === 'leasing') && Boolean(task.questionKey), ) .map((task) => task.questionKey as string), ) } function hasPublishBacklog(): boolean { const snapshot = getPublishSchedulerSnapshot() return snapshot.queuedCount + snapshot.leasingCount > 0 } function activeAdmissionCount(): number { const publish = getPublishSchedulerSnapshot() const monitor = getMonitorSchedulerSnapshot() return ( state.activeExecutions.size + publish.leasingCount + monitor.leasingCount + state.leaseInFlightKinds.size ) } function hasTotalCapacityForNewExecution(): boolean { return ( activeAdmissionCount() < resolveAdaptiveTotalConcurrency() && shouldAdmitNewExecutionUnderRuntimePressure() ) } function canStartAnotherPublishExecution(): boolean { const snapshot = getPublishSchedulerSnapshot() return ( snapshot.queuedCount > 0 && !isLeaseInFlight('publish') && hasTotalCapacityForNewExecution() ) } function shouldPullPublishFallback(): boolean { return ( !state.dispatchWsConnected && Date.now() >= state.publishFallbackBackoffUntil && !isLeaseInFlight('publish') && !hasPublishBacklog() && activePublishCount() === 0 && hasTotalCapacityForNewExecution() ) } function canStartAnotherMonitorExecution(): boolean { if (hasPublishBacklog() || isLeaseInFlight('publish') || isLeaseInFlight('monitor')) { return false } const limit = resolveAdaptiveMonitorConcurrency() if (limit <= 0) { return false } const scheduler = getMonitorSchedulerSnapshot() return activeMonitorCount() + scheduler.leasingCount < limit && hasTotalCapacityForNewExecution() } function resolveLegacyMonitoringLeaseLimit(): number { if (!canStartAnotherMonitorExecution()) { return 0 } const snapshot = getMonitorSchedulerSnapshot() if (snapshot.queuedCount > 0 || snapshot.leasingCount > 0) { return 0 } // Legacy monitoring leases cannot be extended while they sit in the local // scheduler, so only pull one when it can start immediately. if (activeMonitorCount() > 0) { return 0 } return 1 } function resolveAdaptiveMonitorConcurrency(): number { const backgroundBudget = resolveMonitorBackgroundBudget() if (backgroundBudget <= 0) { return 0 } const monitorAccountCount = state.accounts.filter( (account) => isAIPlatformId(account.platform) && getProjectedAccountHealth({ accountId: account.id, platform: account.platform, health: account.health, verifiedAt: account.verified_at, }).health === 'live', ).length if (monitorAccountCount <= 1) { return Math.min(monitorAccountCount, backgroundBudget) } const metrics = getProcessMetricsSnapshot().latestSample if (!metrics) { return Math.min(1, backgroundBudget) } const totalCPUUsage = metrics.processes.reduce((sum, item) => sum + item.percentCPUUsage, 0) const totalPrivateBytesKB = metrics.totals.privateBytes const processCount = metrics.totals.processCount const mainPrivateBytesKB = metrics.mainProcess.memory?.private ?? 0 if ( totalCPUUsage >= 180 || totalPrivateBytesKB >= 2_500_000 || mainPrivateBytesKB >= 1_000_000 || processCount >= 40 ) { return Math.min(1, backgroundBudget) } return Math.min(2, backgroundBudget) } function resolveMonitorBackgroundBudget(): number { if (hasPublishBacklog() || isLeaseInFlight('publish')) { return 0 } return Math.max(0, resolveAdaptiveTotalConcurrency() - publishReservedSlots) } function resolveAdaptiveTotalConcurrency(): number { const hardwareCap = resolveHardwareTotalConcurrency() const runtimeCap = resolveRuntimeHealthTotalConcurrency(hardwareCap) const featureFlagCap = resolveFeatureFlagTotalConcurrency() ?? maximumRuntimeTotalConcurrency return clampInteger( Math.min(hardwareCap, runtimeCap, featureFlagCap), featureFlagCap <= 1 ? 1 : minimumForegroundTotalConcurrency, maximumRuntimeTotalConcurrency, ) } function resolveHardwareTotalConcurrency(): number { const cpuCount = Math.max(1, cpus().length) const memoryGB = totalmem() / 1024 / 1024 / 1024 if (cpuCount >= 10 && memoryGB >= 32) { return 4 } if (cpuCount >= 6 && memoryGB >= 16) { return 3 } return 2 } function resolveRuntimeHealthTotalConcurrency(hardwareCap: number): number { return hardwareCap } function shouldAdmitNewExecutionUnderRuntimePressure(): boolean { const metrics = getProcessMetricsSnapshot().latestSample if (!metrics) { return true } const totalCPUUsage = metrics.processes.reduce((sum, item) => sum + item.percentCPUUsage, 0) const totalPrivateBytesKB = metrics.totals.privateBytes const processCount = metrics.totals.processCount const mainPrivateBytesKB = metrics.mainProcess.memory?.private ?? 0 if ( totalCPUUsage >= 180 || totalPrivateBytesKB >= 2_500_000 || mainPrivateBytesKB >= 1_000_000 || processCount >= 40 ) { return activeAdmissionCount() < minimumForegroundTotalConcurrency } return true } function resolveFeatureFlagTotalConcurrency(): number | null { const raw = process.env.GEO_DESKTOP_MAX_TOTAL_CONCURRENCY?.trim() if (!raw) { return null } const parsed = Number.parseInt(raw, 10) if (!Number.isFinite(parsed)) { return null } return clampInteger(parsed, 1, maximumRuntimeTotalConcurrency) } function clampInteger(value: number, min: number, max: number): number { return Math.min(max, Math.max(min, Math.floor(value))) } function selectPublishAdapter(platform: string): PublishAdapter | null { if (platform === toutiaoAdapter.platform) { return toutiaoAdapter } if (platform === baijiahaoAdapter.platform) { return baijiahaoAdapter } if (platform === sohuhaoAdapter.platform) { return sohuhaoAdapter } if (platform === zhihuAdapter.platform) { return zhihuAdapter } if (platform === wangyihaoAdapter.platform) { return wangyihaoAdapter } if (platform === jianshuAdapter.platform) { return jianshuAdapter } if (platform === juejinAdapter.platform) { return juejinAdapter } if (platform === qiehaoAdapter.platform) { return qiehaoAdapter } if (platform === bilibiliAdapter.platform) { return bilibiliAdapter } if (platform === smzdmAdapter.platform) { return smzdmAdapter } if (platform === weixinGzhAdapter.platform) { return weixinGzhAdapter } if (platform === zolAdapter.platform) { return zolAdapter } if (platform === dongchediAdapter.platform) { return dongchediAdapter } return null } function selectMonitorAdapter(platform: string): MonitorAdapter | null { return getMonitorAdapter(platform) } function normalizeSession( session: DesktopRuntimeSessionSyncRequest | null, ): DesktopRuntimeSessionSyncRequest | null { if (!session) { return null } return { api_base_url: normalizeDesktopApiBaseURL(session.api_base_url), mode: session.mode, client_token: session.client_token?.trim() || null, desktop_client: session.desktop_client ? { ...session.desktop_client } : null, } } function sameSession( left: DesktopRuntimeSessionSyncRequest | null, right: DesktopRuntimeSessionSyncRequest | null, ): boolean { if (!left && !right) { return true } if (!left || !right) { return false } return ( left.api_base_url === right.api_base_url && left.mode === right.mode && left.client_token === right.client_token && left.desktop_client?.id === right.desktop_client?.id ) } function canRunLive( session: DesktopRuntimeSessionSyncRequest | null, ): session is DesktopRuntimeSessionSyncRequest { return Boolean( session && session.mode === 'authenticated' && session.client_token && session.desktop_client, ) } function resolveAccountName(accountId: string, fallback = '待同步账号'): string { const account = state.accounts.find((item) => item.id === accountId) return account?.display_name ?? fallback } function currentClientID(): string { return state.client?.id ?? state.session?.desktop_client?.id ?? '' } function resolveTaskTitle( task: DesktopTaskInfo, payload: Record, existing?: RuntimeTaskRecord, ): string { const title = typeof payload.title === 'string' ? payload.title.trim() : '' if (title) { return title } return existing?.title ?? defaultTaskTitle(task.kind, task.platform) } function describeRouting(routing: RuntimeTaskRouting): string { return routing === 'rabbitmq-primary' ? '主消息队列通道' : '数据库兜底拉取' } function defaultTaskTitle(kind: 'publish' | 'monitor', platform?: string): string { return `${kind === 'publish' ? '发布任务' : '监控任务'}${platform ? ` · ${platform}` : ''}` } function summaryFromTaskStatus(status: RuntimeTaskStatus): string { switch (status) { case 'queued': return '任务已排队,等待客户端领取租约。' case 'in_progress': return '任务正在执行,结果回写需要携带有效租约凭证。' case 'succeeded': return '任务执行成功。' case 'failed': return '任务执行失败。' case 'aborted': return '任务已被取消。' default: return '任务执行异常,已按失败处理。' } } function summaryFromEventType( type: DesktopTaskEventMessage['type'], status: RuntimeTaskStatus, ): string { switch (type) { case 'task_available': return '主消息队列事件已送达当前客户端,等待领取租约。' case 'task_leased': return '服务端已确认租约归属。' case 'task_extended': return '任务租约已续期。' case 'task_canceled': return '任务已被取消。' case 'task_reconciled': return '任务已完成人工对账并回写。' default: return summaryFromTaskStatus(status) } } function inferPlatformFromAccount(accountId: string | null): string { if (!accountId) { return 'desktop' } return state.accounts.find((item) => item.id === accountId)?.platform ?? 'desktop' } function buildApiUrl(baseURL: string, pathname: string): string { const normalized = baseURL.endsWith('/') ? baseURL : `${baseURL}/` return new URL(pathname.replace(/^\//, ''), normalized).toString() } function normalizeJsonObject( value: Record | null | undefined, ): Record { if (!value) { return {} } return { ...value } } function normalizeJsonObjectOrNull( value: Record | null | undefined, ): Record | null { if (!value) { return null } return { ...value } } function asOptionalString(value: unknown): string | undefined { if (typeof value !== 'string') { return undefined } const normalized = value.trim() return normalized || undefined } function asOptionalBoolean(value: unknown): boolean | undefined { return typeof value === 'boolean' ? value : undefined } function asOptionalNumber(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined } function asStringArray(value: unknown): string[] | undefined { if (!Array.isArray(value)) { return undefined } const normalized = value .filter((item): item is string => typeof item === 'string') .map((item) => item.trim()) .filter((item) => item.length > 0) return normalized.length > 0 ? normalized : undefined } function asRecord(value: unknown): Record | undefined { if (!value || typeof value !== 'object' || Array.isArray(value)) { return undefined } return value as Record } function asSourceItems(value: unknown): MonitoringSourceItem[] | undefined { if (!Array.isArray(value)) { return undefined } const items: MonitoringSourceItem[] = [] for (const entry of value) { const record = asRecord(entry) const url = asOptionalString(record?.url) if (!record || !url) { continue } const item: MonitoringSourceItem = { url } const title = asOptionalString(record.title) const siteName = asOptionalString(record.site_name) const siteKey = asOptionalString(record.site_key) const normalizedURL = asOptionalString(record.normalized_url) const host = asOptionalString(record.host) const registrableDomain = asOptionalString(record.registrable_domain) const subdomain = asOptionalString(record.subdomain) const suffix = asOptionalString(record.suffix) const articleID = asOptionalNumber(record.article_id) const publishRecordID = asOptionalNumber(record.publish_record_id) const resolutionStatus = asOptionalString(record.resolution_status) const resolutionConfidence = asOptionalString(record.resolution_confidence) if (title !== undefined) { item.title = title } if (siteName !== undefined) { item.site_name = siteName } if (siteKey !== undefined) { item.site_key = siteKey } if (normalizedURL !== undefined) { item.normalized_url = normalizedURL } if (host !== undefined) { item.host = host } if (registrableDomain !== undefined) { item.registrable_domain = registrableDomain } if (subdomain !== undefined) { item.subdomain = subdomain } if (suffix !== undefined) { item.suffix = suffix } if (articleID !== undefined) { item.article_id = articleID } if (publishRecordID !== undefined) { item.publish_record_id = publishRecordID } if (resolutionStatus !== undefined) { item.resolution_status = resolutionStatus } if (resolutionConfidence !== undefined) { item.resolution_confidence = resolutionConfidence } items.push(item) } return items.length > 0 ? items : undefined } function toInterruptGeneration(value: unknown): number { return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0 } function toStructuredError(error: unknown): Record { if (error instanceof ApiClientError) { return { code: error.code, message: error.message, detail: error.detail ?? null, request_id: error.requestId ?? null, status: error.status ?? null, } } if (error instanceof Error) { return { code: 'desktop_runtime_error', message: error.message, name: error.name, } } return { code: 'desktop_runtime_error', message: String(error), } } function errorMessage(error: unknown): string { if (error instanceof ApiClientError) { return error.detail ? `${error.message}: ${error.detail}` : error.message } if (error instanceof Error) { return error.message } return String(error) } function parseTimestamp(value: string | null | undefined): number | null { if (!value) { return null } const timestamp = Date.parse(value) return Number.isNaN(timestamp) ? null : timestamp } function isDesktopTaskEvent(value: unknown): value is DesktopTaskEventMessage { if (typeof value !== 'object' || value === null) { return false } const event = value as Partial return Boolean( typeof event.type === 'string' && event.type !== 'task_control' && typeof event.task_id === 'string' && event.task_id.trim() !== '' && typeof event.target_client_id === 'string' && event.target_client_id.trim() !== '' && (event.kind === 'publish' || event.kind === 'monitor') && typeof event.status === 'string' && typeof event.updated_at === 'string', ) } function isMonitoringDispatchSignal(value: unknown): value is DesktopTaskEventMessage { if (typeof value !== 'object' || value === null) { return false } const event = value as Partial return ( event.kind === 'monitor' && event.type === 'task_available' && event.signal_only === true && typeof event.target_client_id === 'string' && event.target_client_id.trim() !== '' ) } function isMonitoringTaskControlEvent(value: unknown): value is DesktopTaskEventMessage { if (typeof value !== 'object' || value === null) { return false } const event = value as Partial return ( event.kind === 'monitor' && event.type === 'task_control' && event.control === 'interrupt_requested' && typeof event.task_id === 'string' && event.task_id.trim() !== '' && typeof event.target_client_id === 'string' && event.target_client_id.trim() !== '' ) } function isConnectedEvent(value: unknown): value is { server_time: string } { if (typeof value !== 'object' || value === null) { return false } return typeof (value as { server_time?: string }).server_time === 'string' } function isReconnectPayload(value: unknown): value is { delayMs: number } { if (typeof value !== 'object' || value === null) { return false } return typeof (value as { delayMs?: number }).delayMs === 'number' }