feat(monitoring): fail same-client queued tasks on desktop authorization failure
Desktop Client Build / Resolve Build Metadata (push) Successful in 44s
Backend CI / Backend (push) Successful in 17m5s
Desktop Client Build / Build Desktop Client (push) Successful in 25m13s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 42s

When a desktop monitoring task fails with a non-retryable authorization failure
(login expired, challenge required, risk control), all pending same-client/same-platform
tasks for the same business day are immediately bulk-failed as non-retryable, avoiding
wasted execution attempts. Adds preflight authorization checks before task execution,
enriches failure payloads with disposition metadata (code, category, retryable flags),
and triggers account health reports on auth state degradation.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 23:50:50 +08:00
parent 5bbbbc5cf1
commit 28633cf570
7 changed files with 940 additions and 118 deletions
@@ -29,8 +29,8 @@ import {
forgetTrackedAccountHealth,
getAccountHealthSnapshot,
getProjectedAccountHealth,
markTrackedAccountMissingPartition,
markTrackedAccountBound,
markTrackedAccountMissingPartition,
probeTrackedAccount,
reportAccountFailure,
syncTrackedAccounts,
@@ -407,6 +407,137 @@ function accountActionRequiredSummary(
return `${label} 账号需要完成人机验证后才能继续执行。`
}
function markAuthorizationFailureNonRetryable(
error: Record<string, JsonValue>,
category: 'ai_platform_authorization' | 'media_account_authorization',
): Record<string, JsonValue> {
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<AdapterExecutionResult | null> {
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,
@@ -1363,7 +1494,8 @@ export function noteRuntimeAccountBound(account: DesktopAccountInfo): void {
const existingIndex = state.accounts.findIndex(
(item) =>
item.id === normalizedAccount.id ||
(isAIPlatformId(normalizedAccount.platform) && item.platform === normalizedAccount.platform) ||
(isAIPlatformId(normalizedAccount.platform) &&
item.platform === normalizedAccount.platform) ||
(item.platform === normalizedAccount.platform &&
sameAccountPlatformUid(item.platform_uid, normalizedAccount.platform_uid)),
)
@@ -1804,7 +1936,12 @@ async function executeLeasedMonitoringTask(
queueMicrotask(() => pumpExecutionLoop())
try {
const execution = await executeTaskAdapter(taskRecord, abortController.signal)
const execution =
(await resolveAuthorizationPreflightBlock(taskRecord)) ??
withAuthorizationRetryPolicy(
taskRecord,
await executeTaskAdapter(taskRecord, abortController.signal),
)
if (execution.status === 'unknown') {
const skipReason = monitoringSkipReasonFromExecution(execution)
await skipMonitoringTask(monitoringTaskID, {
@@ -1937,6 +2074,14 @@ function buildMonitoringResultPayload(
): 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,
@@ -1944,7 +2089,7 @@ function buildMonitoringResultPayload(
provider_request_id: asOptionalString(payload.provider_request_id),
request_id: asOptionalString(payload.request_id),
answer: asOptionalString(payload.answer),
raw_response_json: asRecord(payload.raw_response_json),
raw_response_json: failedRawResponseJSON,
citations: asSourceItems(payload.citations),
search_results: asSourceItems(payload.search_results),
brand_mentioned: asOptionalBoolean(payload.brand_mentioned),
@@ -1961,12 +2106,20 @@ function buildMonitoringFailurePayload(
leaseToken: string,
failureMessage: string,
structuredError: Record<string, JsonValue>,
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: structuredError,
runtime_error: runtimeError,
},
error_message: failureMessage,
completed_at: new Date().toISOString(),
@@ -2007,7 +2160,9 @@ async function executeLeasedDesktopMonitorTask(
}
try {
const execution = await executeTaskAdapter(taskRecord, signal)
const execution =
(await resolveAuthorizationPreflightBlock(taskRecord)) ??
withAuthorizationRetryPolicy(taskRecord, await executeTaskAdapter(taskRecord, signal))
if (execution.status === 'unknown') {
const skipReason = monitoringSkipReasonFromExecution(execution)
await skipMonitoringTask(monitoringTaskID, {
@@ -2172,6 +2327,7 @@ async function executeLeasedDesktopMonitorTask(
leased.lease_token as string,
failureMessage || '监控任务执行失败。',
structuredError,
taskRecord,
),
)
} catch (submitError) {
@@ -2402,7 +2558,12 @@ async function executeLeasedTask(
return
}
const execution = await executeTaskAdapter(taskRecord, abortController.signal)
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,
@@ -2679,16 +2840,19 @@ function buildAccountBlockedResult(
return {
status: 'failed',
summary: accountActionRequiredSummary(task.platform, readiness.authReason),
error: {
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',
},
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',
),
}
}
@@ -2696,10 +2860,13 @@ function buildAccountBlockedResult(
return {
status: 'failed',
summary: `${task.accountName} 登录态已失效,请重新授权后再试。`,
error: {
code: 'desktop_account_auth_expired',
message: 'desktop account auth expired',
},
error: markAuthorizationFailureNonRetryable(
{
code: 'desktop_account_auth_expired',
message: 'desktop account auth expired',
},
isAIPlatformId(task.platform) ? 'ai_platform_authorization' : 'media_account_authorization',
),
}
}
@@ -2737,6 +2904,9 @@ async function maybeReportTaskAuthFailure(
})
maybeRecordAccountAccessAlert(task, accountIdentity, previousSnapshot, nextSnapshot)
if (nextSnapshot && nextSnapshot.authState !== 'active') {
enqueueAccountHealthReport(accountIdentity.id)
}
}
function resolvePlaywrightTargetURL(platform: string, payload: Record<string, JsonValue>): string {