feat(monitoring): add DeepSeek monitoring end-to-end

- desktop: new DeepSeek page adapter and monitor registry
- desktop: extract generic AI auth helpers into shared module and
  skip binding when challenge signals are present
- admin-web: render DeepSeek HTML answers with inline citation anchors
  and surface reference-number badges on source cards
- server: fall back to inline_links/source_panel_links and read
  text/siteName fields when extracting DeepSeek citations

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 22:54:56 +08:00
parent f6889ecdee
commit f6e553dcc3
12 changed files with 2971 additions and 65 deletions
+10 -40
View File
@@ -31,6 +31,11 @@ import {
isWindowReadyForDetection,
loadWindowURLSafely,
} from "./external-window";
import {
buildGenericAIPageFingerprintFallback,
genericAIPageLooksAuthenticated,
type GenericAIPageState,
} from "./generic-ai-auth";
import { upsertDesktopAccount } from "./transport/api-client";
import { STANDARD_USER_AGENT } from "./user-agent";
import {
@@ -681,18 +686,6 @@ function sanitizeDetectedAccount(account: DetectedAccount): DetectedAccount {
};
}
interface GenericAIPageState {
displayName: string | null;
avatarUrl: string | null;
fingerprint: string | null;
credentialFingerprint: string | null;
currentPath: string | null;
strongAuthSignalCount: number;
loginSignalCount: number;
loggedOutSignalCount: number;
challengeSignalCount: number;
}
async function readGenericAIPageState(
webContents: WebContents | null | undefined,
): Promise<GenericAIPageState | null> {
@@ -1004,10 +997,6 @@ function safeParseURL(input: string): URL | null {
}
}
function genericAIConversationPath(pathname: string): boolean {
return /^\/(?:chat|c|conversation)(?:\/|$)/i.test(pathname);
}
function isLikelyGenericAICredentialName(name: string): boolean {
const normalized = name.trim().toLowerCase();
if (/^(x[-_]?csrf[-_]?token|xsrf[-_]?token|csrf[-_]?token|csrf|_csrf)$/i.test(normalized)) {
@@ -1029,29 +1018,6 @@ function isLikelyGenericAICredentialValue(value: string | null | undefined): boo
return normalized.length >= 8;
}
function genericAIPageLooksAuthenticated(currentURL: string, pageState: GenericAIPageState | null): boolean {
const current = safeParseURL(currentURL);
const currentPath = normalizeURLPath(current?.pathname ?? pageState?.currentPath ?? "");
const strongAuthSignalCount = pageState?.strongAuthSignalCount ?? 0;
const loginSignalCount = pageState?.loginSignalCount ?? 0;
const loggedOutSignalCount = pageState?.loggedOutSignalCount ?? 0;
const onConversationPath = genericAIConversationPath(currentPath);
if (loggedOutSignalCount > 0) {
return false;
}
if (loginSignalCount > 0 && strongAuthSignalCount === 0 && !onConversationPath) {
return false;
}
if (loginSignalCount > 0) {
return strongAuthSignalCount >= 2 && !onConversationPath;
}
return strongAuthSignalCount > 0 || onConversationPath;
}
async function buildGenericAISessionFingerprint(
platformId: string,
session: Session,
@@ -1087,7 +1053,7 @@ async function buildGenericAISessionFingerprint(
}
if (parts.length === 0) {
return null;
return buildGenericAIPageFingerprintFallback(platformId, pageState);
}
return boundedIdentity(`${platformId}-session`, parts.join("||"));
@@ -1115,6 +1081,9 @@ async function detectGenericAIPlatform(
}
const pageState = await readGenericAIPageState(context.webContents).catch(() => null);
if ((pageState?.challengeSignalCount ?? 0) > 0) {
return null;
}
if (!genericAIPageLooksAuthenticated(currentURL, pageState)) {
return null;
}
@@ -2692,6 +2661,7 @@ export async function bindPublishAccount(platformId: string): Promise<DesktopAcc
);
const detectAndBind = async () => {
syncDetectionState();
const currentURL = window.isDestroyed() ? "" : window.webContents.getURL();
const canProbe = detectReady || /^https?:\/\//i.test(currentURL);
if (finished || checking || !canProbe || window.isDestroyed()) {