feat(desktop): add Wenxin monitoring adapter and surface platform risk control

Introduces the Wenxin (文心一言) monitoring adapter and registers it in
the runtime controller and adapter index. Adds a new risk_control
failure classification with custom classifiers for Doubao and Wenxin
that recognize rate-limit, 频控 and 访问环境异常 signals, and propagates
this state through account-health, runtime activity alerts, and the
renderer views so users see "触发风控" instead of a generic challenge
message on the Home, Accounts, and AI Platforms screens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 15:26:24 +08:00
parent bd4ee8feef
commit ca40657c5a
12 changed files with 2552 additions and 21 deletions
@@ -21,6 +21,7 @@ import {
kimiAdapter,
qwenAdapter,
toutiaoAdapter,
wenxinAdapter,
yuanbaoAdapter,
zhihuAdapter,
type AdapterExecutionResult,
@@ -34,6 +35,7 @@ import {
import {
ensureAccountReady,
forgetTrackedAccountHealth,
getAccountHealthSnapshot,
getProjectedAccountHealth,
probeTrackedAccount,
reportAccountFailure,
@@ -127,7 +129,7 @@ const pullIntervalMs = 60_000;
const leaseExtendIntervalMs = 60_000;
const maxActivityItems = 60;
const monitorBusinessTimeZone = "Asia/Shanghai";
const authRequiredMonitorPlatforms = new Set(["yuanbao", "kimi", "deepseek", "doubao"]);
const authRequiredMonitorPlatforms = new Set(["yuanbao", "kimi", "deepseek", "doubao", "wenxin"]);
interface RuntimeTaskRecord {
id: string;
@@ -279,6 +281,53 @@ function isDefinitiveAuthState(authState: string): boolean {
return ["active", "expired", "challenge_required", "revoked"].includes(authState);
}
function runtimePlatformLabel(platform: string): string {
return getAIPlatformCatalogItem(platform)?.label ?? platform;
}
function accountActionRequiredSummary(
platform: string,
authReason: string | null | undefined,
): string {
const label = runtimePlatformLabel(platform);
if (authReason === "risk_control") {
return `${label} 账号疑似触发风控,请打开平台处理验证或等待限制解除后再继续执行。`;
}
return `${label} 账号需要完成人机验证后才能继续执行。`;
}
function maybeRecordAccountAccessAlert(
task: RuntimeTaskRecord,
accountIdentity: PublishAccountIdentity | null,
previous: ReturnType<typeof getAccountHealthSnapshot>,
next: ReturnType<typeof getAccountHealthSnapshot>,
): void {
if (!accountIdentity || !next) {
return;
}
if (previous?.authState === next.authState && previous?.authReason === next.authReason) {
return;
}
if (next.authState === "challenge_required" && next.authReason === "risk_control") {
recordActivity(
"warn",
`${runtimePlatformLabel(accountIdentity.platform)} 触发风控`,
`${accountIdentity.displayName || task.accountName || "当前账号"} 已被平台限制,请打开平台处理验证或等待限制解除后再刷新状态。`,
);
return;
}
if (next.authState === "challenge_required") {
recordActivity(
"warn",
`${runtimePlatformLabel(accountIdentity.platform)} 需要人工验证`,
`${accountIdentity.displayName || task.accountName || "当前账号"} 需要打开平台完成人工验证后再继续执行任务。`,
);
}
}
function isoFromTimestamp(value: number | null): string | null {
return value ? new Date(value).toISOString() : null;
}
@@ -2209,10 +2258,10 @@ function buildAccountBlockedResult(
if (readiness.authState === "challenge_required") {
return {
status: "failed",
summary: `${task.accountName} 需要完成人机验证后才能继续执行。`,
summary: accountActionRequiredSummary(task.platform, readiness.authReason),
error: {
code: "desktop_account_challenge_required",
message: "desktop account challenge required",
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",
},
};
}
@@ -2247,7 +2296,8 @@ async function maybeReportTaskAuthFailure(
return;
}
await reportAccountFailure(accountIdentity, {
const previousSnapshot = getAccountHealthSnapshot(accountIdentity.id);
const nextSnapshot = await reportAccountFailure(accountIdentity, {
summary: result.summary,
error: result.error ?? null,
}).catch((error) => {
@@ -2257,7 +2307,10 @@ async function maybeReportTaskAuthFailure(
platform: accountIdentity.platform,
message: error instanceof Error ? error.message : String(error),
});
return null;
});
maybeRecordAccountAccessAlert(task, accountIdentity, previousSnapshot, nextSnapshot);
}
function resolvePlaywrightTargetURL(
@@ -2640,6 +2693,9 @@ function selectMonitorAdapter(platform: string): MonitorAdapter | null {
if (platform === qwenAdapter.provider) {
return qwenAdapter;
}
if (platform === wenxinAdapter.provider) {
return wenxinAdapter;
}
return null;
}