feat: Implement Qwen adapter and enhance AI platform detection

- Added a new Qwen adapter to facilitate monitoring through Playwright, leveraging internal text/chat managers.
- Updated account detection logic to recognize Qwen sessions based on persisted cookies, improving binding reliability.
- Relaxed authentication requirements for monitor tasks, allowing anonymous execution for certain AI platforms.
- Enhanced the generic AI platform detection to include Qwen-specific logic, ensuring accurate session identification.
- Modified the monitoring dashboard to include runtime state indicating if the current user's desktop client is online.
- Updated various files including `account-binder.ts`, `runtime-controller.ts`, and `monitoring_service.go` to support new features and improvements.
This commit is contained in:
2026-04-20 19:10:35 +08:00
parent 69fd182755
commit 7abac1e9c4
13 changed files with 973 additions and 105 deletions
+103 -2
View File
@@ -1009,6 +1009,10 @@ function genericAIConversationPath(pathname: string): boolean {
}
function isLikelyGenericAICredentialName(name: string): boolean {
const normalized = name.trim().toLowerCase();
if (/^(x[-_]?csrf[-_]?token|xsrf[-_]?token|csrf[-_]?token|csrf|_csrf)$/i.test(normalized)) {
return false;
}
return /(token|session|auth|login|passport|ticket|jwt|refresh|access)/i.test(name);
}
@@ -1126,6 +1130,103 @@ async function detectGenericAIPlatform(
});
}
async function detectQwenFromSession(
session: Session,
hints: {
displayName?: string | null;
avatarUrl?: string | null;
} = {},
): Promise<DetectedAccount | null> {
const cookies = await session.cookies.get({ url: "https://www.qianwen.com/" }).catch(() => []);
if (!cookies.length) {
return null;
}
const cookieMap = new Map<string, string>();
for (const cookie of cookies) {
if (!cookieMap.has(cookie.name)) {
cookieMap.set(cookie.name, cookie.value);
}
}
const ticketHash = normalizeText(cookieMap.get("tongyi_sso_ticket_hash"));
const ticket = normalizeText(cookieMap.get("tongyi_sso_ticket"));
const platformUid = ticketHash ?? ticket;
if (!platformUid) {
return null;
}
return sanitizeDetectedAccount({
platformUid,
displayName: hints.displayName ?? "通义千问账号",
avatarUrl: hints.avatarUrl ?? null,
});
}
async function detectQwenPlatform(
platformId: string,
label: string,
context: DetectContext,
): Promise<DetectedAccount | null> {
const genericDetected = await detectGenericAIPlatform(platformId, label, context);
if (genericDetected) {
return genericDetected;
}
if (!context.webContents || context.webContents.isDestroyed()) {
return null;
}
const currentURL = context.webContents.getURL();
const platformMeta = aiPlatformCatalog.find((item) => item.id === platformId) ?? null;
if (!platformMeta) {
return null;
}
if (currentURL && looksLikeLoginRedirect(currentURL, platformMeta.loginUrl)) {
return null;
}
const pageState = await readGenericAIPageState(context.webContents).catch(() => null);
if ((pageState?.challengeSignalCount ?? 0) > 0) {
return null;
}
if ((pageState?.loggedOutSignalCount ?? 0) > 0) {
return null;
}
const fromSession = await detectQwenFromSession(context.session, {
displayName: pageState?.displayName ?? `${label} 会话`,
avatarUrl: pageState?.avatarUrl ?? null,
});
if (fromSession) {
return fromSession;
}
const fingerprint = await buildGenericAISessionFingerprint(platformId, context.session, pageState);
if (!fingerprint) {
return null;
}
return sanitizeDetectedAccount({
platformUid: fingerprint,
displayName: pageState?.displayName ?? `${label} 会话`,
avatarUrl: pageState?.avatarUrl ?? null,
});
}
async function detectAIPlatformAccount(
platformId: string,
label: string,
context: DetectContext,
): Promise<DetectedAccount | null> {
if (platformId === "qwen") {
return await detectQwenPlatform(platformId, label, context);
}
return await detectGenericAIPlatform(platformId, label, context);
}
async function detectToutiao({ session, webContents }: DetectContext): Promise<DetectedAccount | null> {
const pageResponse = await pageFetchJson<ToutiaoMediaInfoResponse>(
webContents,
@@ -1537,7 +1638,7 @@ export async function probePublishAccountSession(
};
}
const detected = await detectGenericAIPlatform(account.platform, definition.label, {
const detected = await detectAIPlatformAccount(account.platform, definition.label, {
session: handle.session,
webContents: window.webContents,
});
@@ -2329,7 +2430,7 @@ const aiBindingDefinitions: Record<string, PublishPlatformBindingDefinition> = O
label: platform.label,
loginUrl: platform.loginUrl,
consoleUrl: platform.consoleUrl,
detect: (context: DetectContext) => detectGenericAIPlatform(platform.id, platform.label, context),
detect: (context: DetectContext) => detectAIPlatformAccount(platform.id, platform.label, context),
},
]),
) as Record<string, PublishPlatformBindingDefinition>;