feat: add code queue services and baidu netdisk

This commit is contained in:
Codex
2026-05-13 00:59:36 +00:00
parent ae462ed9ef
commit 6a04144d3f
63 changed files with 10983 additions and 1101 deletions
+469 -127
View File
@@ -37,7 +37,8 @@ const NETWORK_CHECK_NAMES = [
"network:met-nonlinear-public-blocked",
"network:claudeqq-public-blocked",
"network:todo-note-public-blocked",
"network:codex-queue-public-blocked",
"network:code-queue-public-blocked",
"network:filebrowser-public-blocked",
] as const;
const SERVICE_CHECK_NAMES = [
@@ -49,6 +50,7 @@ const SERVICE_CHECK_NAMES = [
"provider:system-status",
"provider:process-resource-status",
"provider:docker-status",
"provider:gateway-restart-policy",
"provider:upgrade-plan",
"provider-ingress:public-health",
"microservice:catalog-findjob",
@@ -56,7 +58,11 @@ const SERVICE_CHECK_NAMES = [
"microservice:catalog-met-nonlinear",
"microservice:catalog-claudeqq",
"microservice:catalog-todo-note",
"microservice:catalog-codex-queue",
"microservice:catalog-code-queue",
"microservice:catalog-filebrowser",
"microservice:filebrowser-health",
"microservice:filebrowser-webui",
"microservice:filebrowser-d601-health",
"microservice:findjob-status",
"microservice:findjob-health",
"microservice:findjob-summary",
@@ -79,9 +85,9 @@ const SERVICE_CHECK_NAMES = [
"microservice:todo-note-health",
"microservice:todo-note-migrated-data",
"microservice:todo-note-write-path",
"microservice:codex-queue-status",
"microservice:codex-queue-health",
"microservice:codex-queue-tasks",
"microservice:code-queue-status",
"microservice:code-queue-health",
"microservice:code-queue-tasks",
] as const;
const DATABASE_CHECK_NAMES = [
@@ -112,10 +118,12 @@ const FRONTEND_CHECK_NAMES = [
"frontend:microservice-catalog-visible",
"frontend:todo-note-integrated-visible",
"frontend:findjob-integrated-visible",
"frontend:codex-queue-integrated-visible",
"frontend:codex-queue-initial-prompt-full-expand",
"frontend:codex-queue-trace-full-load",
"frontend:codex-queue-judge-wrap",
"frontend:code-queue-integrated-visible",
"frontend:code-queue-summary-mobile-wrap",
"frontend:code-queue-initial-prompt-full-expand",
"frontend:code-queue-trace-full-load",
"frontend:code-queue-judge-wrap",
"frontend:code-queue-error-red-markers",
"frontend:claudeqq-integrated-visible",
"frontend:url-route-deeplink",
"frontend:pipeline-integrated-visible",
@@ -269,13 +277,18 @@ type OverflowProbe = {
ok: boolean;
documentOverflowX: number;
offenders: Array<{
overflowKind: string;
overflowPx: number;
tag: string;
selector: string;
className: string;
testId: string;
text: string;
left: number;
right: number;
width: number;
clientWidth: number;
scrollWidth: number;
parentClassName: string;
}>;
};
@@ -294,7 +307,7 @@ const LAYOUT_OVERFLOW_PAGE_TEST_IDS: Record<string, string> = {
"/app/pipeline/": "pipeline-page",
"/app/met-nonlinear/": "met-nonlinear-page",
"/app/claudeqq/": "claudeqq-page",
"/app/codex-queue/": "codex-queue-page",
"/app/code-queue/": "code-queue-page",
};
function layoutOverflowTargets(): Array<{ label: string; path: string; testId: string }> {
@@ -315,42 +328,94 @@ async function collectLayoutOverflow(page: Page, viewport: { width: number; heig
for (const target of targets) {
await page.goto(`${origin}${target.path}`, { waitUntil: "domcontentloaded", timeout: 15000 });
await page.waitForSelector(`[data-testid="${target.testId}"]`, { timeout: 15000 }).catch(() => undefined);
await page.waitForTimeout(160);
await page.waitForLoadState("networkidle", { timeout: 1200 }).catch(() => undefined);
await page.waitForTimeout(180);
const result = await page.evaluate(({ viewportName: currentViewport, label, path, testId }) => {
const overflowTolerancePx = 6;
const viewportWidth = document.documentElement.clientWidth;
const checkNestedContainerOverflow = currentViewport === "mobile" || viewportWidth <= 600;
const documentOverflowX = Math.max(0, document.documentElement.scrollWidth - viewportWidth, document.body.scrollWidth - viewportWidth);
const allowed = (element: Element): boolean => {
const html = element as HTMLElement;
if (html.closest(".raw-dialog, .raw-json, .performance-table-wrap, .process-table-wrap, .docker-table-wrap, .table-wrap, .met-project-table, .pipeline-flow-frame, .react-flow, .codex-transcript, .codex-task-list, .todo-tree-scroll, .tabs, .rail")) return true;
const style = getComputedStyle(html);
return ["auto", "scroll"].includes(style.overflowX);
const intentionalHorizontalScrollSelector = [
".raw-json",
".performance-table-wrap",
".process-table-wrap",
".docker-table-wrap",
".table-wrap",
".met-project-table",
".pipeline-flow-frame",
".react-flow",
".pipeline-gantt-viewport",
".codex-edit-diff",
".tabs",
".rail",
".status-strip",
".top-status-bar",
".codex-transcript-item.ran .codex-transcript-command",
".codex-transcript-item.ran .codex-transcript-body",
".codex-transcript-item.explored .codex-transcript-command",
".codex-transcript-item.explored .codex-transcript-body",
".codex-transcript-item.edited .codex-transcript-command",
".codex-transcript-item.edited .codex-transcript-body",
].join(",");
const insideIntentionalHorizontalScroll = (element: Element): boolean => Boolean(element.closest(intentionalHorizontalScrollSelector));
const selectorFor = (html: HTMLElement): string => {
const tag = html.tagName.toLowerCase();
const test = html.getAttribute("data-testid");
const id = html.id ? `#${html.id}` : "";
const className = String(html.className || "");
const classes = className
.split(/\s+/)
.map((item) => item.trim())
.filter(Boolean)
.slice(0, 3)
.map((item) => `.${item}`)
.join("");
return `${tag}${id}${classes}${test ? `[data-testid="${test}"]` : ""}`;
};
const offenders = Array.from(document.body.querySelectorAll("*")).flatMap((element) => {
const html = element as HTMLElement;
if (!(html instanceof HTMLElement) || allowed(html)) return [];
const offenderFor = (html: HTMLElement, overflowKind: string, overflowPx: number) => {
const rect = html.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return [];
const excessLeft = rect.left < -2;
const excessRight = rect.right > viewportWidth + 2;
if (!excessLeft && !excessRight) return [];
const parent = html.parentElement;
return [{
return {
overflowKind,
overflowPx: Math.round(overflowPx),
tag: html.tagName.toLowerCase(),
selector: selectorFor(html).slice(0, 180),
className: String(html.className || "").slice(0, 160),
testId: html.getAttribute("data-testid") || "",
text: (html.innerText || html.textContent || "").replace(/\s+/g, " ").trim().slice(0, 160),
left: Math.round(rect.left),
right: Math.round(rect.right),
width: Math.round(rect.width),
clientWidth: Math.round(html.clientWidth),
scrollWidth: Math.round(html.scrollWidth),
parentClassName: String(parent?.className || "").slice(0, 160),
}];
}).slice(0, 20);
};
};
const offenders = Array.from(document.body.querySelectorAll("*")).flatMap((element) => {
const html = element as HTMLElement;
if (!(html instanceof HTMLElement) || insideIntentionalHorizontalScroll(html)) return [];
const style = getComputedStyle(html);
const rect = html.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return [];
const excessLeft = rect.left < -overflowTolerancePx;
const excessRight = rect.right > viewportWidth + overflowTolerancePx;
const viewportOverflowPx = Math.max(0, -rect.left, rect.right - viewportWidth);
const containerOverflowPx = Math.max(0, html.scrollWidth - html.clientWidth);
const formControl = ["INPUT", "SELECT", "TEXTAREA"].includes(html.tagName);
const clippedOverflow = ["clip", "hidden"].includes(style.overflowX);
const found: ReturnType<typeof offenderFor>[] = [];
if (excessLeft || excessRight) found.push(offenderFor(html, "viewport", viewportOverflowPx));
if (checkNestedContainerOverflow && !formControl && !clippedOverflow && html.clientWidth > 0 && containerOverflowPx > overflowTolerancePx) found.push(offenderFor(html, "container", containerOverflowPx));
return found;
})
.sort((a, b) => b.overflowPx - a.overflowPx)
.slice(0, 20);
return {
viewport: currentViewport,
label,
path,
testId,
ok: documentOverflowX <= 2 && offenders.length === 0,
ok: documentOverflowX <= overflowTolerancePx && offenders.length === 0,
documentOverflowX: Math.round(documentOverflowX),
offenders,
};
@@ -620,7 +685,7 @@ function dockerPortSummary(): unknown {
}
function dockerStatusCheckDetail(dockerStatus: unknown, providerId: string): unknown {
const response = dockerStatus as { ok?: boolean; status?: number; body?: { dockerStatuses?: Array<{ providerId?: string; name?: string; nodeStatus?: string; updatedAt?: string; dockerStatus?: { ok?: boolean; socketPresent?: boolean; collectedAt?: string; counts?: unknown; daemon?: unknown; containers?: Array<{ id?: string; name?: string; image?: string; state?: string; status?: string; ports?: string }> } }> } };
const response = dockerStatus as { ok?: boolean; status?: number; body?: { dockerStatuses?: Array<{ providerId?: string; name?: string; nodeStatus?: string; updatedAt?: string; dockerStatus?: { ok?: boolean; socketPresent?: boolean; collectedAt?: string; counts?: unknown; daemon?: unknown; containers?: Array<{ id?: string; name?: string; image?: string; state?: string; status?: string; ports?: string; restartPolicy?: string; pidMode?: string }> } }> } };
const item = response.body?.dockerStatuses?.find((entry) => entry.providerId === providerId);
return {
ok: response.ok,
@@ -641,11 +706,57 @@ function dockerStatusCheckDetail(dockerStatus: unknown, providerId: string): unk
state: container.state,
status: container.status,
ports: container.ports,
restartPolicy: container.restartPolicy,
pidMode: container.pidMode,
})),
},
};
}
function providerGatewayRuntimeGuardDetail(dockerStatus: unknown): { ok: boolean; gateways: unknown[]; violations: unknown[] } {
const response = dockerStatus as {
body?: {
dockerStatuses?: Array<{
providerId?: string;
nodeStatus?: string;
dockerStatus?: {
containers?: Array<{
id?: string;
name?: string;
image?: string;
state?: string;
restartPolicy?: string;
pidMode?: string;
composeService?: string;
composeProject?: string;
}>;
};
}>;
};
};
const gateways = (response.body?.dockerStatuses ?? []).flatMap((entry) =>
(entry.dockerStatus?.containers ?? [])
.filter((container) => String(container.name || "").includes("provider-gateway") || container.composeService === "provider-gateway")
.map((container) => ({
providerId: entry.providerId,
nodeStatus: entry.nodeStatus,
id: container.id,
name: container.name,
image: container.image,
state: container.state,
restartPolicy: container.restartPolicy,
pidMode: container.pidMode,
composeProject: container.composeProject,
composeService: container.composeService,
}))
);
const violations = gateways.filter((container) =>
(container as { state?: unknown }).state === "running"
&& ((container as { restartPolicy?: unknown }).restartPolicy !== "always" || (container as { pidMode?: unknown }).pidMode !== "host")
);
return { ok: gateways.length > 0 && violations.length === 0, gateways, violations };
}
function systemStatusCheckDetail(systemStatus: unknown, providerId: string): unknown {
const response = systemStatus as { ok?: boolean; status?: number; body?: { systemStatuses?: Array<{ providerId?: string; name?: string; nodeStatus?: string; updatedAt?: string; current?: { ok?: boolean; collectedAt?: string; cpu?: unknown; memory?: unknown; disk?: unknown }; history?: unknown[] }> } };
const item = response.body?.systemStatuses?.find((entry) => entry.providerId === providerId);
@@ -676,7 +787,8 @@ async function exposureChecks(config: UniDeskConfig, urls: PublicUrls, checks: E
const metNonlinearPublic = await fetchProbe(`http://${config.network.publicHost}:3288/health`, 2500);
const claudeqqPublic = await fetchProbe(`http://${config.network.publicHost}:3290/health`, 2500);
const todoNotePublic = await fetchProbe(`http://${config.network.publicHost}:4211/api/health`, 2500);
const codexQueuePublic = await fetchProbe(`http://${config.network.publicHost}:14222/health`, 2500);
const codeQueuePublic = await fetchProbe(`http://${config.network.publicHost}:14222/health`, 2500);
const filebrowserPublic = await fetchProbe(`http://${config.network.publicHost}:4251/health`, 2500);
addSelectedCheck(checks, options, "network:only-frontend-provider-ports", !portsText.includes(`:${config.network.core.port}->`) && !portsText.includes(`:${config.network.database.port}->`) && !portsText.includes(":14222->"), portSummary);
addSelectedCheck(checks, options, "network:core-public-blocked", (corePublic as { reachable?: boolean }).reachable === false, corePublic);
addSelectedCheck(checks, options, "network:database-public-blocked", (databasePublic as { reachable?: boolean }).reachable === false, databasePublic);
@@ -684,7 +796,8 @@ async function exposureChecks(config: UniDeskConfig, urls: PublicUrls, checks: E
addSelectedCheck(checks, options, "network:met-nonlinear-public-blocked", (metNonlinearPublic as { reachable?: boolean }).reachable === false, metNonlinearPublic);
addSelectedCheck(checks, options, "network:claudeqq-public-blocked", (claudeqqPublic as { reachable?: boolean }).reachable === false, claudeqqPublic);
addSelectedCheck(checks, options, "network:todo-note-public-blocked", (todoNotePublic as { reachable?: boolean }).reachable === false, todoNotePublic);
addSelectedCheck(checks, options, "network:codex-queue-public-blocked", (codexQueuePublic as { reachable?: boolean }).reachable === false, codexQueuePublic);
addSelectedCheck(checks, options, "network:code-queue-public-blocked", (codeQueuePublic as { reachable?: boolean }).reachable === false, codeQueuePublic);
addSelectedCheck(checks, options, "network:filebrowser-public-blocked", (filebrowserPublic as { reachable?: boolean }).reachable === false, filebrowserPublic);
}
async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2ECheck[], options: E2ERunOptions): Promise<void> {
@@ -715,9 +828,12 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
const todoNoteStatus = dockerCoreJson("/api/microservices/todo-note/status");
const todoNoteHealth = dockerCoreJson("/api/microservices/todo-note/health");
const todoNoteInstances = dockerCoreJson("/api/microservices/todo-note/proxy/api/instances");
const codexQueueStatus = dockerCoreJson("/api/microservices/codex-queue/status");
const codexQueueHealth = dockerCoreJson("/api/microservices/codex-queue/health");
const codexQueueTasks = dockerCoreJson("/api/microservices/codex-queue/proxy/api/tasks?limit=5");
const codeQueueStatus = dockerCoreJson("/api/microservices/code-queue/status");
const codeQueueHealth = dockerCoreJson("/api/microservices/code-queue/health");
const codeQueueTasks = dockerCoreJson("/api/microservices/code-queue/proxy/api/tasks?limit=5");
const filebrowserHealth = dockerCoreJson("/api/microservices/filebrowser/health");
const filebrowserWebui = dockerCoreJson("/api/microservices/filebrowser/proxy/");
const filebrowserD601Health = dockerCoreJson("/api/microservices/filebrowser-d601/health");
const todoE2eName = `E2E Todo ${Date.now()}`;
const todoNoteCreate = dockerCoreJson("/api/microservices/todo-note/proxy/api/instances", { method: "POST", body: { name: todoE2eName } });
const todoCreatedId = (todoNoteCreate as { body?: { id?: string } }).body?.id ?? "";
@@ -737,7 +853,7 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
: { ok: false, error: "missing created todo note id" };
const providerIngress = await fetchProbe(urls.providerIngressHealthUrl);
const overviewBody = (coreOverview as { body?: { ok?: boolean; dbReady?: boolean; onlineNodeCount?: number; pgdata?: { volumeName?: string; databaseBytes?: number } } }).body;
const corePerformanceBody = (corePerformance as { body?: { ok?: boolean; requests?: { componentSummary?: unknown[] }; operations?: { summary?: unknown[] }; database?: { pgdata?: { volumeName?: string }; codexQueueStorage?: { table?: string } } } }).body;
const corePerformanceBody = (corePerformance as { body?: { ok?: boolean; requests?: { componentSummary?: unknown[] }; operations?: { summary?: unknown[] }; database?: { pgdata?: { volumeName?: string }; codeQueueStorage?: { table?: string } } } }).body;
const nodeList = (coreNodes as { body?: { nodes?: Array<{ providerId?: string; status?: string; labels?: Record<string, unknown> }> } }).body?.nodes ?? [];
const mainNode = nodeList.find((node) => node.providerId === config.providerGateway.id);
const expectedGatewayVersion = providerGatewayPackageVersion();
@@ -747,21 +863,25 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
const processMemoryDescending = mainProcesses.length < 2 || mainProcesses.every((row, index, rows) => index === 0 || Number(rows[index - 1]?.rssBytes ?? 0) >= Number(row.rssBytes ?? 0));
const dockerStatuses = (dockerStatus as { body?: { dockerStatuses?: Array<{ providerId?: string; dockerStatus?: { counts?: { containers?: number }; containers?: unknown[] } }> } }).body?.dockerStatuses ?? [];
const mainDocker = dockerStatuses.find((item) => item.providerId === config.providerGateway.id);
const providerGatewayRuntimeGuard = providerGatewayRuntimeGuardDetail(dockerStatus);
addSelectedCheck(checks, options, "core:internal-overview", (coreOverview as { ok?: boolean }).ok === true && overviewBody?.ok === true && overviewBody.dbReady === true && (overviewBody.onlineNodeCount ?? 0) >= 1, coreOverview);
addSelectedCheck(checks, options, "core:pgdata-usage", (coreOverview as { ok?: boolean }).ok === true && overviewBody?.pgdata?.volumeName === config.database.volume && Number(overviewBody.pgdata.databaseBytes ?? 0) > 0, coreOverview);
addSelectedCheck(checks, options, "core:performance-api", (corePerformance as { ok?: boolean }).ok === true && corePerformanceBody?.ok === true && Array.isArray(corePerformanceBody.requests?.componentSummary) && Array.isArray(corePerformanceBody.operations?.summary) && corePerformanceBody.database?.pgdata?.volumeName === config.database.volume && corePerformanceBody.database?.codexQueueStorage?.table === "unidesk_codex_queue_tasks", corePerformance);
addSelectedCheck(checks, options, "core:performance-api", (corePerformance as { ok?: boolean }).ok === true && corePerformanceBody?.ok === true && Array.isArray(corePerformanceBody.requests?.componentSummary) && Array.isArray(corePerformanceBody.operations?.summary) && corePerformanceBody.database?.pgdata?.volumeName === config.database.volume && corePerformanceBody.database?.codeQueueStorage?.table === "unidesk_code_queue_tasks", corePerformance);
addSelectedCheck(checks, options, "provider:self-node-online", nodeList.some((node) => node.providerId === config.providerGateway.id && node.status === "online"), coreNodes);
addSelectedCheck(checks, options, "provider:gateway-version-label", mainNode?.labels?.providerGatewayVersion === expectedGatewayVersion && mainNode?.labels?.providerGatewayUpgradePolicy === "always-enabled", { providerId: config.providerGateway.id, expectedGatewayVersion, labels: mainNode?.labels ?? null });
addSelectedCheck(checks, options, "provider:system-status", (systemStatus as { ok?: boolean }).ok === true && mainSystem?.current !== undefined && Number.isFinite(mainSystem.current.cpu?.percent) && Number.isFinite(mainSystem.current.memory?.percent) && mainSystem.current.memory?.mode === "actual_without_cache" && Number.isFinite(mainSystem.current.memory?.cacheBytes) && Number.isFinite(mainSystem.current.disk?.percent) && (mainSystem.history?.length ?? 0) > 0, systemStatusCheckDetail(systemStatus, config.providerGateway.id));
addSelectedCheck(checks, options, "provider:process-resource-status", mainProcesses.length > 0 && mainSystem?.current?.processSummary?.defaultSort === "memory_desc" && processMemoryDescending && mainProcesses.some((row) => Number.isFinite(row.pid) && Number.isFinite(row.rssBytes) && Number.isFinite(row.cpuPercent) && typeof row.command === "string"), { providerId: config.providerGateway.id, processSummary: mainSystem?.current?.processSummary, sample: mainProcesses.slice(0, 5) });
addSelectedCheck(checks, options, "provider:docker-status", (dockerStatus as { ok?: boolean }).ok === true && mainDocker?.dockerStatus !== undefined && ((mainDocker.dockerStatus.counts?.containers ?? 0) > 0 || (mainDocker.dockerStatus.containers?.length ?? 0) > 0), dockerStatusCheckDetail(dockerStatus, config.providerGateway.id));
addSelectedCheck(checks, options, "provider:gateway-restart-policy", providerGatewayRuntimeGuard.ok, providerGatewayRuntimeGuard);
const microserviceList = (microservices as { body?: { microservices?: Array<{ id?: string; providerId?: string; backend?: { public?: boolean }; runtime?: { providerStatus?: string; container?: { name?: string; state?: string } } }> } }).body?.microservices ?? [];
const findjob = microserviceList.find((service) => service.id === "findjob");
const pipeline = microserviceList.find((service) => service.id === "pipeline");
const metNonlinear = microserviceList.find((service) => service.id === "met-nonlinear");
const claudeqq = microserviceList.find((service) => service.id === "claudeqq");
const todoNote = microserviceList.find((service) => service.id === "todo-note");
const codexQueue = microserviceList.find((service) => service.id === "codex-queue");
const codeQueue = microserviceList.find((service) => service.id === "code-queue");
const filebrowser = microserviceList.find((service) => service.id === "filebrowser");
const filebrowserD601 = microserviceList.find((service) => service.id === "filebrowser-d601");
const findjobSummaryBody = (findjobSummary as { body?: { totalJobs?: number; prioritizedJobs?: number } }).body;
const findjobJobs = (findjobJobsPreview as { body?: { jobs?: unknown[]; _unidesk?: { arrayLimits?: { jobs?: { returnedLength?: number; originalLength?: number } } } } }).body;
const todoNoteRows = (todoNoteInstances as { body?: { instances?: Array<{ id?: string; name?: string; todoCount?: number; completedCount?: number }> } }).body?.instances ?? [];
@@ -776,8 +896,11 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
const claudeqqNapcatLoginBody = (claudeqqNapcatLogin as { body?: { ok?: boolean; napcat?: { containerized?: boolean; loginState?: string; qrcode?: { available?: boolean; dataUrl?: string } }; login?: { ready?: boolean; state?: string } } }).body;
const claudeqqEventsBody = (claudeqqEvents as { body?: { ok?: boolean; events?: unknown[]; count?: number } }).body;
const claudeqqSubscriptionsBody = (claudeqqSubscriptions as { body?: { ok?: boolean; subscriptions?: unknown[]; count?: number } }).body;
const codexQueueHealthBody = (codexQueueHealth as { body?: { ok?: boolean; queue?: { defaultModel?: string; judgeConfigured?: boolean; modelReasoningEfforts?: Record<string, string> } } }).body;
const codexQueueTasksBody = (codexQueueTasks as { body?: { ok?: boolean; queue?: { defaultModel?: string; modelReasoningEfforts?: Record<string, string> }; tasks?: unknown[] } }).body;
const codeQueueHealthBody = (codeQueueHealth as { body?: { ok?: boolean; queue?: { defaultModel?: string; judgeConfigured?: boolean; modelReasoningEfforts?: Record<string, string> } } }).body;
const codeQueueTasksBody = (codeQueueTasks as { body?: { ok?: boolean; queue?: { defaultModel?: string; modelReasoningEfforts?: Record<string, string> }; tasks?: unknown[] } }).body;
const filebrowserHealthBody = (filebrowserHealth as { body?: { status?: string } }).body;
const filebrowserD601HealthBody = (filebrowserD601Health as { body?: { status?: string } }).body;
const filebrowserWebuiText = String((filebrowserWebui as { body?: { text?: string } }).body?.text || "");
const firstPipelineRun = Array.isArray(pipelineSnapshotBody?.runs)
? pipelineSnapshotBody.runs[0] as { runId?: string; pipelineId?: string; status?: string; updatedAt?: string } | undefined
: undefined;
@@ -804,7 +927,16 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
addSelectedCheck(checks, options, "microservice:catalog-met-nonlinear", (microservices as { ok?: boolean }).ok === true && metNonlinear?.providerId === "D601" && metNonlinear.backend?.public === false && metNonlinear.runtime?.container?.name === "met-nonlinear-ts", { microservices });
addSelectedCheck(checks, options, "microservice:catalog-claudeqq", (microservices as { ok?: boolean }).ok === true && claudeqq?.providerId === "D601" && claudeqq.backend?.public === false && claudeqq.runtime?.container?.name === "claudeqq-backend", { microservices });
addSelectedCheck(checks, options, "microservice:catalog-todo-note", (microservices as { ok?: boolean }).ok === true && todoNote?.providerId === config.providerGateway.id && todoNote.backend?.public === false && todoNote.runtime?.container?.name === "todo-note-backend", { microservices });
addSelectedCheck(checks, options, "microservice:catalog-codex-queue", (microservices as { ok?: boolean }).ok === true && codexQueue?.providerId === config.providerGateway.id && codexQueue.backend?.public === false && codexQueue.runtime?.container?.name === "codex-queue-backend", { microservices });
addSelectedCheck(checks, options, "microservice:catalog-code-queue", (microservices as { ok?: boolean }).ok === true && codeQueue?.providerId === config.providerGateway.id && codeQueue.backend?.public === false && codeQueue.runtime?.container?.name === "code-queue-backend", { microservices });
addSelectedCheck(checks, options, "microservice:catalog-filebrowser", (microservices as { ok?: boolean }).ok === true
&& filebrowser?.providerId === "D518"
&& filebrowser.backend?.public === false
&& filebrowser.runtime?.container?.name === "unidesk-filebrowser-d518"
&& filebrowserD601?.providerId === "D601",
{ filebrowser, filebrowserD601 });
addSelectedCheck(checks, options, "microservice:filebrowser-health", (filebrowserHealth as { ok?: boolean }).ok === true && filebrowserHealthBody?.status === "OK", filebrowserHealth);
addSelectedCheck(checks, options, "microservice:filebrowser-webui", (filebrowserWebui as { ok?: boolean; status?: number }).ok === true && (filebrowserWebui as { status?: number }).status === 200 && filebrowserWebuiText.includes("File Browser"), { status: (filebrowserWebui as { status?: number }).status, textPreview: filebrowserWebuiText.slice(0, 600) });
addSelectedCheck(checks, options, "microservice:filebrowser-d601-health", (filebrowserD601Health as { ok?: boolean }).ok === true && filebrowserD601HealthBody?.status === "OK", filebrowserD601Health);
addSelectedCheck(checks, options, "microservice:findjob-status", (findjobStatus as { ok?: boolean }).ok === true && (findjobStatus as { body?: { microservice?: { id?: string; providerId?: string } } }).body?.microservice?.providerId === "D601", findjobStatus);
addSelectedCheck(checks, options, "microservice:findjob-health", (findjobHealth as { ok?: boolean; body?: { ok?: boolean } }).ok === true && (findjobHealth as { body?: { ok?: boolean } }).body?.ok === true, findjobHealth);
addSelectedCheck(checks, options, "microservice:findjob-summary", (findjobSummary as { ok?: boolean }).ok === true && Number.isFinite(findjobSummaryBody?.totalJobs) && Number.isFinite(findjobSummaryBody?.prioritizedJobs), findjobSummary);
@@ -847,9 +979,9 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
addSelectedCheck(checks, options, "microservice:todo-note-health", (todoNoteHealth as { ok?: boolean; body?: { ok?: boolean; storage?: string } }).ok === true && (todoNoteHealth as { body?: { ok?: boolean; storage?: string } }).body?.ok === true && (todoNoteHealth as { body?: { storage?: string } }).body?.storage === "postgres", todoNoteHealth);
addSelectedCheck(checks, options, "microservice:todo-note-migrated-data", (todoNoteInstances as { ok?: boolean }).ok === true && todoNoteRows.length >= 5 && ["CONSTAR", "大论文", "找工作", "小论文", "事务"].every((name) => todoNoteNames.includes(name)) && todoNoteRows.reduce((sum, row) => sum + Number(row.todoCount ?? 0), 0) >= 100, { todoNoteInstances });
addSelectedCheck(checks, options, "microservice:todo-note-write-path", (todoNoteCreate as { ok?: boolean }).ok === true && (todoNoteAdd as { ok?: boolean }).ok === true && (todoNoteToggle as { ok?: boolean }).ok === true && (todoNoteUndo as { ok?: boolean }).ok === true && (todoNoteDelete as { ok?: boolean }).ok === true, { todoNoteCreate, todoNoteAdd, todoNoteToggle, todoNoteUndo, todoNoteDelete });
addSelectedCheck(checks, options, "microservice:codex-queue-status", (codexQueueStatus as { ok?: boolean }).ok === true && (codexQueueStatus as { body?: { microservice?: { id?: string; providerId?: string } } }).body?.microservice?.providerId === config.providerGateway.id, codexQueueStatus);
addSelectedCheck(checks, options, "microservice:codex-queue-health", (codexQueueHealth as { ok?: boolean }).ok === true && codexQueueHealthBody?.ok === true && codexQueueHealthBody.queue?.defaultModel === "gpt-5.5" && codexQueueHealthBody.queue?.modelReasoningEfforts?.["gpt-5.5"] === "xhigh", codexQueueHealth);
addSelectedCheck(checks, options, "microservice:codex-queue-tasks", (codexQueueTasks as { ok?: boolean }).ok === true && codexQueueTasksBody?.ok === true && Array.isArray(codexQueueTasksBody.tasks) && codexQueueTasksBody.queue?.defaultModel === "gpt-5.5" && codexQueueTasksBody.queue?.modelReasoningEfforts?.["gpt-5.5"] === "xhigh", codexQueueTasks);
addSelectedCheck(checks, options, "microservice:code-queue-status", (codeQueueStatus as { ok?: boolean }).ok === true && (codeQueueStatus as { body?: { microservice?: { id?: string; providerId?: string } } }).body?.microservice?.providerId === config.providerGateway.id, codeQueueStatus);
addSelectedCheck(checks, options, "microservice:code-queue-health", (codeQueueHealth as { ok?: boolean }).ok === true && codeQueueHealthBody?.ok === true && codeQueueHealthBody.queue?.defaultModel === "gpt-5.5" && codeQueueHealthBody.queue?.modelReasoningEfforts?.["gpt-5.5"] === "xhigh", codeQueueHealth);
addSelectedCheck(checks, options, "microservice:code-queue-tasks", (codeQueueTasks as { ok?: boolean }).ok === true && codeQueueTasksBody?.ok === true && Array.isArray(codeQueueTasksBody.tasks) && codeQueueTasksBody.queue?.defaultModel === "gpt-5.5" && codeQueueTasksBody.queue?.modelReasoningEfforts?.["gpt-5.5"] === "xhigh", codeQueueTasks);
const upgradeDispatch = dockerCoreJson("/api/dispatch", {
method: "POST",
body: { providerId: config.providerGateway.id, command: "provider.upgrade", payload: { source: "cli-e2e", mode: "plan" } },
@@ -947,12 +1079,14 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
const needMicroserviceCatalog = wants("frontend:microservice-catalog-visible");
const needTodoNote = wants("frontend:todo-note-integrated-visible");
const needFindJob = wants("frontend:findjob-integrated-visible");
const needCodexQueue = wantsAny([
"frontend:codex-queue-integrated-visible",
"frontend:codex-queue-initial-prompt-full-expand",
"frontend:codex-queue-trace-full-load",
"frontend:codex-queue-judge-wrap",
]);
const needCodeQueue = wantsAny([
"frontend:code-queue-integrated-visible",
"frontend:code-queue-summary-mobile-wrap",
"frontend:code-queue-initial-prompt-full-expand",
"frontend:code-queue-trace-full-load",
"frontend:code-queue-judge-wrap",
"frontend:code-queue-error-red-markers",
]);
const needClaudeqq = wants("frontend:claudeqq-integrated-visible");
const needRouteDeepLink = wants("frontend:url-route-deeplink");
const needPipeline = wantsAny([
@@ -1027,20 +1161,24 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
let microserviceCatalogText = "";
let todoNoteText = "";
let findjobText = "";
let codexQueueText = "";
let codexQueueOutputText = "";
let codexQueueTaskCount = 0;
let codexQueueOptions: string[] = [];
let codexQueueSwitchMetrics: any = { optionCount: 0, switched: false };
let codexQueueSubmitQueueControl: any = { tagName: "", createButtonVisible: false, oldInputMissing: false };
let codexQueueTracePlacement: any = { firstChildIsTrace: false, noPageTopStatus: false, filterInsideTracePanel: false, traceStatusVisible: false, markAllReadVisible: false };
let codexQueueGlobalStatus: any = { activeMicroserviceVisible: false };
let codexQueuePromptDefaultEmpty = false;
let codexQueueSubmitGuard: any = { batchRowVisible: false, disabledBeforeConfirm: false, enabledAfterConfirm: false, waitElementMissingBeforeSubmit: false };
let codexQueueScrollbarMetrics: any = { transcriptThin: false, toolHorizontalHidden: true };
let codeQueueText = "";
let codeQueueOutputText = "";
let codeQueueTaskCount = 0;
let codeQueueOptions: string[] = [];
let codeQueueSwitchMetrics: any = { optionCount: 0, switched: false };
let codeQueueSubmitQueueControl: any = { tagName: "", createButtonVisible: false, oldInputMissing: false };
let codeQueueTracePlacement: any = { firstChildIsTrace: false, noPageTopStatus: false, filterInsideTracePanel: false, traceStatusVisible: false, markAllReadVisible: false };
let codeQueueGlobalStatus: any = { activeMicroserviceVisible: false };
let codeQueueSidebarUpdateMetrics: any = { cardCount: 0, labels: [], hasRecentUpdateLabel: false };
let codeQueueHtmlGuard: any = { rootAttrMissing: false, sourceAttrMissing: false, sourceNoBasePrompt: false };
let codeQueueSummaryMobileMetrics: any = { checked: false, summaryCount: 0, ok: false };
let codeQueuePromptDefaultEmpty = false;
let codeQueueSubmitGuard: any = { batchRowVisible: false, disabledBeforeConfirm: false, enabledAfterConfirm: false, waitElementMissingBeforeSubmit: false };
let codeQueueScrollbarMetrics: any = { transcriptThin: false, toolHorizontalHidden: true };
let codexInitialPromptFullMetrics: any = { candidateFound: false };
let codexTraceFullMetrics: any = { candidateFound: false };
let codexJudgeWrapMetrics: any = { checked: false };
let codeQueueErrorHighlightMetrics: any = { checked: false, candidateFound: false };
let claudeqqText = "";
let routeDeepLinkText = "";
let routeInitialPath = "";
@@ -1237,9 +1375,9 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
}
}
if (needMicroserviceCatalog || needTodoNote || needFindJob || needCodexQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
if (needMicroserviceCatalog || needTodoNote || needFindJob || needCodeQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
await page.getByRole("button", { name: /用户服务/ }).click();
if (needMicroserviceCatalog || needTodoNote || needFindJob || needCodexQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
if (needMicroserviceCatalog || needTodoNote || needFindJob || needCodeQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
await page.waitForSelector('[data-testid="microservice-catalog-page"]', { timeout: 10000 });
}
if (needMicroserviceCatalog) {
@@ -1248,7 +1386,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
await page.waitForSelector('[data-testid="microservice-row-met-nonlinear"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="microservice-row-claudeqq"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="microservice-row-todo-note"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="microservice-row-codex-queue"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="microservice-row-code-queue"]', { timeout: 10000 });
microserviceCatalogText = await page.locator('[data-testid="microservice-catalog-page"]').innerText({ timeout: 5000 });
}
if (needTodoNote) {
@@ -1304,13 +1442,13 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
}, undefined, { timeout: 30000 });
findjobText = await page.locator('[data-testid="findjob-page"]').innerText({ timeout: 5000 });
}
if (needCodexQueue) {
await page.getByRole("button", { name: /Codex Queue/ }).click();
await page.waitForSelector('[data-testid="codex-queue-page"]', { timeout: 10000 });
if (needCodeQueue) {
await page.getByRole("button", { name: /Code Queue/ }).click();
await page.waitForSelector('[data-testid="code-queue-page"]', { timeout: 10000 });
await page.waitForFunction(() => {
const text = document.body.innerText;
const lower = text.toLowerCase();
return lower.includes("codex queue")
return lower.includes("code queue")
&& text.includes("gpt-5.4-mini")
&& text.includes("gpt-5.4")
&& text.includes("gpt-5.5")
@@ -1321,24 +1459,27 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& text.includes("打断")
&& lower.includes("attempts");
}, undefined, { timeout: 30000 });
await page.waitForSelector('[data-testid="codex-queue-filter-select"]', { timeout: 10000 });
codexQueueTracePlacement = await page.evaluate(() => ({
firstChildIsTrace: Boolean(document.querySelector('[data-testid="codex-queue-page"] > .codex-session-stage:first-child .codex-output-panel')),
noPageTopStatus: document.querySelector('[data-testid="codex-queue-page"] > [data-testid="codex-top-status"]') === null,
filterInsideTracePanel: Boolean(document.querySelector('.codex-output-panel [data-testid="codex-queue-filter-select"]')),
await page.waitForSelector('[data-testid="code-queue-filter-select"]', { timeout: 10000 });
codeQueueTracePlacement = await page.evaluate(() => ({
firstChildIsTrace: Boolean(document.querySelector('[data-testid="code-queue-page"] > .codex-session-stage:first-child .codex-output-panel')),
noPageTopStatus: document.querySelector('[data-testid="code-queue-page"] > [data-testid="codex-top-status"]') === null,
filterInsideTracePanel: Boolean(document.querySelector('.codex-output-panel [data-testid="code-queue-filter-select"]')),
taskSearchVisible: Boolean(document.querySelector('[data-testid="codex-task-search-input"]')),
traceStatusVisible: /\s*\d+.*\s*\d+.*\s*\d+/s.test(document.querySelector('[data-testid="codex-trace-status-summary"]')?.textContent || ""),
markAllReadVisible: Boolean(document.querySelector('.codex-output-panel [data-testid="codex-mark-all-read-button"]')),
}));
codexQueueGlobalStatus = await page.evaluate(() => {
codeQueueGlobalStatus = await page.evaluate(() => {
const text = document.querySelector('[data-testid="active-microservice-status"]')?.textContent || "";
return { activeMicroserviceVisible: /Codex Queue.*在线|codex queue.*在线/i.test(text), text };
return { activeMicroserviceVisible: /Code Queue.*在线|code queue.*在线/i.test(text), text };
});
await page.waitForSelector('[data-testid="codex-queue-id-select"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="code-queue-id-select"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="codex-create-queue-button"]', { timeout: 10000 });
codexQueueSubmitQueueControl = await page.evaluate(() => {
const select = document.querySelector('[data-testid="codex-queue-id-select"]') as HTMLSelectElement | null;
codeQueueSubmitQueueControl = await page.evaluate(() => {
const select = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null;
const button = document.querySelector('[data-testid="codex-create-queue-button"]') as HTMLButtonElement | null;
const prompt = document.querySelector('[data-testid="codex-queue-task-form"] textarea') as HTMLTextAreaElement | null;
const prompt = document.querySelector('[data-testid="code-queue-task-form"] textarea') as HTMLTextAreaElement | null;
const provider = document.querySelector('[data-testid="codex-provider-select"]') as HTMLSelectElement | null;
const cwd = document.querySelector('[data-testid="codex-cwd-input"]') as HTMLInputElement | null;
const maxAttempts = document.querySelector('[data-testid="codex-max-attempts-input"]') as HTMLInputElement | null;
const moveSelect = document.querySelector('[data-testid="codex-task-queue-move-select"]') as HTMLSelectElement | null;
const moveButton = document.querySelector('[data-testid="codex-task-queue-move-button"]') as HTMLButtonElement | null;
@@ -1346,17 +1487,20 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
tagName: select?.tagName.toLowerCase() || "",
optionCount: select?.options.length ?? 0,
createButtonVisible: Boolean(button && button.offsetParent !== null),
oldInputMissing: document.querySelector('[data-testid="codex-queue-id-input"]') === null,
oldInputMissing: document.querySelector('[data-testid="code-queue-id-input"]') === null,
promptDefaultEmpty: (prompt?.value || "") === "",
providerValue: provider?.value || "",
providerOptions: Array.from(provider?.options || []).map((option) => ({ value: option.value, text: option.textContent || "" })),
cwdValue: cwd?.value || "",
maxAttemptsMax: maxAttempts?.max || "",
maxAttemptsValue: maxAttempts?.value || "",
moveQueueVisible: Boolean(moveSelect && moveSelect.offsetParent !== null && moveButton && moveButton.offsetParent !== null),
};
});
codexQueuePromptDefaultEmpty = Boolean(codexQueueSubmitQueueControl.promptDefaultEmpty);
await page.locator('[data-testid="codex-queue-task-form"] textarea').fill("e2e batch guard one\n---\ne2e batch guard two");
codeQueuePromptDefaultEmpty = Boolean(codeQueueSubmitQueueControl.promptDefaultEmpty);
await page.locator('[data-testid="code-queue-task-form"] textarea').fill("e2e batch guard one\n---\ne2e batch guard two");
await page.waitForSelector('[data-testid="codex-batch-confirm-row"]', { timeout: 5000 });
codexQueueSubmitGuard = await page.evaluate(() => {
codeQueueSubmitGuard = await page.evaluate(() => {
const row = document.querySelector('[data-testid="codex-batch-confirm-row"]') as HTMLElement | null;
const checkbox = document.querySelector('[data-testid="codex-batch-confirm-checkbox"]') as HTMLInputElement | null;
const button = document.querySelector('[data-testid="codex-enqueue-button"]') as HTMLButtonElement | null;
@@ -1374,8 +1518,8 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
const button = document.querySelector('[data-testid="codex-enqueue-button"]') as HTMLButtonElement | null;
return button !== null && !button.disabled;
}, undefined, { timeout: 5000 });
codexQueueSubmitGuard = {
...codexQueueSubmitGuard,
codeQueueSubmitGuard = {
...codeQueueSubmitGuard,
...(await page.evaluate(() => {
const button = document.querySelector('[data-testid="codex-enqueue-button"]') as HTMLButtonElement | null;
return {
@@ -1385,8 +1529,8 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
})),
};
await page.locator('[data-testid="codex-clear-input-button"]').click();
codexQueueOptions = await page.locator('[data-testid="codex-queue-filter-select"] option').evaluateAll((options) => options.map((option) => (option as HTMLOptionElement).textContent || ""));
codexQueueSwitchMetrics = await page.locator('[data-testid="codex-queue-filter-select"] option').evaluateAll((options) => ({
codeQueueOptions = await page.locator('[data-testid="code-queue-filter-select"] option').evaluateAll((options) => options.map((option) => (option as HTMLOptionElement).textContent || ""));
codeQueueSwitchMetrics = await page.locator('[data-testid="code-queue-filter-select"] option').evaluateAll((options) => ({
optionCount: options.length,
queueValues: options.map((option) => (option as HTMLOptionElement).value).filter((value) => value !== "__all__"),
switched: false,
@@ -1397,7 +1541,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
const output = document.querySelector('[data-testid="codex-output"]')?.textContent || "";
return taskCount === 0 || output.includes("Submitted prompt");
}, undefined, { timeout: 15000 });
codexQueueScrollbarMetrics = await page.evaluate(() => {
codeQueueScrollbarMetrics = await page.evaluate(() => {
const transcript = document.querySelector('.codex-transcript') as HTMLElement | null;
const toolBlock = document.querySelector('.codex-transcript-item.ran .codex-transcript-command, .codex-transcript-item.ran .codex-transcript-body, .codex-transcript-item.explored .codex-transcript-command, .codex-transcript-item.explored .codex-transcript-body, .codex-transcript-item.edited .codex-transcript-command, .codex-transcript-item.edited .codex-transcript-body') as HTMLElement | null;
const transcriptStyle = transcript ? getComputedStyle(transcript) : null;
@@ -1411,7 +1555,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
toolOverflowX: toolStyle?.overflowX || "",
};
});
if (wants("frontend:codex-queue-judge-wrap")) {
if (wants("frontend:code-queue-judge-wrap")) {
codexJudgeWrapMetrics = await page.evaluate(() => {
const measure = (element: HTMLElement | null): any => {
if (element === null) return { found: false };
@@ -1489,15 +1633,77 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
};
});
}
codexQueueTaskCount = await page.locator('[data-testid^="codex-task-codex_"]').count();
if (wants("frontend:codex-queue-initial-prompt-full-expand")) {
codeQueueSidebarUpdateMetrics = await page.evaluate(() => {
const cards = Array.from(document.querySelectorAll('[data-testid="codex-session-sidebar"] .codex-task-card')) as HTMLElement[];
const labels = cards
.map((card) => card.querySelector('[data-testid^="codex-task-recent-update-"]')?.textContent || "")
.filter(Boolean);
return {
cardCount: cards.length,
labels,
hasRecentUpdateLabel: cards.length === 0 || labels.some((text) => /^:\s*(|--|\d+|\d+\d+|\d+|\d+)/u.test(text)),
};
});
if (wantsAny(["frontend:code-queue-integrated-visible", "frontend:code-queue-summary-mobile-wrap"])) {
await page.setViewportSize({ width: 390, height: 860 });
await page.waitForTimeout(120);
codeQueueSummaryMobileMetrics = await page.evaluate(() => {
const viewportWidth = document.documentElement.clientWidth;
const summaries = Array.from(document.querySelectorAll(".codex-execution-summary")) as HTMLElement[];
const items = summaries.slice(0, 8).map((summary, index) => {
const header = summary.querySelector(".codex-progressive-card-head") as HTMLElement | null;
const meta = header?.querySelector("code") as HTMLElement | null;
const headerRect = header?.getBoundingClientRect();
const metaRect = meta?.getBoundingClientRect();
const rootOverflowPx = Math.max(0, summary.scrollWidth - summary.clientWidth);
const headerOverflowPx = header ? Math.max(0, header.scrollWidth - header.clientWidth) : 0;
const metaOverflowPx = meta ? Math.max(0, meta.scrollWidth - meta.clientWidth) : 0;
return {
index,
text: (header?.textContent || "").replace(/\s+/g, " ").trim().slice(0, 180),
viewportWidth,
rootClientWidth: Math.round(summary.clientWidth),
rootScrollWidth: Math.round(summary.scrollWidth),
headerClientWidth: header ? Math.round(header.clientWidth) : 0,
headerScrollWidth: header ? Math.round(header.scrollWidth) : 0,
metaClientWidth: meta ? Math.round(meta.clientWidth) : 0,
metaScrollWidth: meta ? Math.round(meta.scrollWidth) : 0,
rootOverflowPx: Math.round(rootOverflowPx),
headerOverflowPx: Math.round(headerOverflowPx),
metaOverflowPx: Math.round(metaOverflowPx),
headerRight: headerRect ? Math.round(headerRect.right) : 0,
metaRight: metaRect ? Math.round(metaRect.right) : 0,
metaWhiteSpace: meta ? getComputedStyle(meta).whiteSpace : "",
metaOverflowWrap: meta ? getComputedStyle(meta).overflowWrap : "",
};
});
return {
checked: true,
viewportWidth,
summaryCount: summaries.length,
items,
ok: summaries.length === 0 || items.every((item) =>
item.rootOverflowPx <= 1
&& item.headerOverflowPx <= 1
&& item.metaOverflowPx <= 1
&& item.headerRight <= viewportWidth + 2
&& item.metaRight <= viewportWidth + 2
&& item.metaWhiteSpace !== "nowrap"
&& (item.metaOverflowWrap === "anywhere" || item.metaOverflowWrap === "break-word")),
};
});
await page.setViewportSize({ width: 1440, height: 920 });
await page.waitForTimeout(120);
}
codeQueueTaskCount = await page.locator('[data-testid^="codex-task-codex_"]').count();
if (wants("frontend:code-queue-initial-prompt-full-expand")) {
codexInitialPromptFullMetrics = await page.evaluate(async () => {
const tasksResponse = await fetch("/api/microservices/codex-queue/proxy/api/tasks?limit=300&lite=1&devReady=0", { credentials: "same-origin" });
const tasksResponse = await fetch("/api/microservices/code-queue/proxy/api/tasks?limit=300&lite=1&devReady=0", { credentials: "same-origin" });
const tasksPayload = await tasksResponse.json().catch(() => null);
const tasks = Array.isArray(tasksPayload?.tasks) ? tasksPayload.tasks : [];
const candidate = tasks.find((task: any) => Array.isArray(task?.referenceTaskIds) && task.referenceTaskIds.length > 0);
if (!candidate?.id) return { candidateFound: false };
const metaResponse = await fetch(`/api/microservices/codex-queue/proxy/api/tasks/${encodeURIComponent(String(candidate.id))}?meta=1`, { credentials: "same-origin" });
const metaResponse = await fetch(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(String(candidate.id))}?meta=1`, { credentials: "same-origin" });
const metaPayload = await metaResponse.json().catch(() => null);
const fullPrompt = String(metaPayload?.task?.prompt || "");
const displayPrompt = String(metaPayload?.task?.displayPrompt || "");
@@ -1506,7 +1712,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
taskId: String(candidate.id),
promptChars: fullPrompt.length,
displayPromptChars: displayPrompt.length,
hasResolvedReference: fullPrompt.includes("# Codex Queue 已解析引用上下文"),
hasResolvedReference: fullPrompt.includes("# Code Queue 已解析引用上下文"),
hasCurrentTaskMarker: fullPrompt.includes("# 本次任务"),
hasReferenceTaskId: /codex_\\d+_[A-Za-z0-9_-]+/u.test(fullPrompt),
};
@@ -1521,7 +1727,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
await page.waitForFunction(() => (document.querySelector('[data-testid="codex-initial-prompt-full"]') as HTMLDetailsElement | null)?.open === true, undefined, { timeout: 5000 });
const initialFullText = await page.getByTestId("codex-initial-prompt-full-text").innerText({ timeout: 5000 });
codexInitialPromptFullMetrics.initialExpanded = await page.getByTestId("codex-initial-prompt-full").evaluate((element) => (element as HTMLDetailsElement).open);
codexInitialPromptFullMetrics.initialFullHasReference = initialFullText.includes("# Codex Queue 已解析引用上下文") || initialFullText.includes("引用 Codex Queue");
codexInitialPromptFullMetrics.initialFullHasReference = initialFullText.includes("# Code Queue 已解析引用上下文") || initialFullText.includes("引用 Code Queue");
codexInitialPromptFullMetrics.initialFullHasCurrentTask = initialFullText.includes("# 本次任务") || initialFullText.includes("本次任务:");
codexInitialPromptFullMetrics.initialFullChars = initialFullText.length;
codexInitialPromptFullMetrics.legacyPromptPanelMissing = await page.evaluate(() =>
@@ -1531,16 +1737,16 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
);
}
}
if (wants("frontend:codex-queue-trace-full-load")) {
if (wants("frontend:code-queue-trace-full-load")) {
codexTraceFullMetrics = await page.evaluate(async () => {
const tasksResponse = await fetch("/api/codex-queue-direct/api/tasks?limit=300&lite=1&devReady=0", { credentials: "same-origin" });
const tasksResponse = await fetch("/api/code-queue-direct/api/tasks?limit=300&lite=1&devReady=0", { credentials: "same-origin" });
const tasksPayload = await tasksResponse.json().catch(() => null);
const tasks = Array.isArray(tasksPayload?.tasks) ? tasksPayload.tasks : [];
const terminal = new Set(["succeeded", "failed", "canceled"]);
for (const task of tasks) {
const taskId = String(task?.id || "");
if (!taskId || !terminal.has(String(task?.status || "")) || Number(task?.outputCount || 0) < 20) continue;
const transcriptResponse = await fetch(`/api/codex-queue-direct/api/tasks/${encodeURIComponent(taskId)}/transcript?afterSeq=0&limit=120&fullText=1`, { credentials: "same-origin" });
const transcriptResponse = await fetch(`/api/code-queue-direct/api/tasks/${encodeURIComponent(taskId)}/transcript?afterSeq=0&limit=120&fullText=1`, { credentials: "same-origin" });
const transcriptPayload = await transcriptResponse.json().catch(() => null);
const transcript = Array.isArray(transcriptPayload?.transcript) ? transcriptPayload.transcript : [];
const toolCount = transcript.filter((line: any) => ["ran", "explored", "edited"].includes(String(line?.kind || ""))).length;
@@ -1569,7 +1775,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
await traceTaskCard.scrollIntoViewIfNeeded({ timeout: 10000 });
await traceTaskCard.click();
await page.waitForFunction(() => {
const pageElement = document.querySelector('[data-testid="codex-queue-page"]') as HTMLElement | null;
const pageElement = document.querySelector('[data-testid="code-queue-page"]') as HTMLElement | null;
const output = document.querySelector('[data-testid="codex-output"]') as HTMLElement | null;
const toolCount = output?.querySelectorAll('.codex-transcript-item.ran, .codex-transcript-item.explored, .codex-transcript-item.edited').length ?? 0;
return pageElement?.getAttribute("data-load-state") === "complete" && toolCount >= 8;
@@ -1577,7 +1783,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
codexTraceFullMetrics = {
...codexTraceFullMetrics,
...(await page.evaluate(() => {
const pageElement = document.querySelector('[data-testid="codex-queue-page"]') as HTMLElement | null;
const pageElement = document.querySelector('[data-testid="code-queue-page"]') as HTMLElement | null;
const output = document.querySelector('[data-testid="codex-output"]') as HTMLElement | null;
const text = output?.textContent || "";
return {
@@ -1594,13 +1800,144 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
};
}
}
codexQueueOutputText = await page.locator('[data-testid="codex-output"]').innerText({ timeout: 5000 });
codexQueueText = await page.locator('[data-testid="codex-queue-page"]').innerText({ timeout: 5000 });
codexQueueOptions = await page.locator('[data-testid="codex-queue-filter-select"] option').evaluateAll((options) => options.map((option) => (option as HTMLOptionElement).textContent || ""));
codexQueueSubmitQueueControl = await page.evaluate(() => {
const select = document.querySelector('[data-testid="codex-queue-id-select"]') as HTMLSelectElement | null;
if (wants("frontend:code-queue-error-red-markers")) {
const authCookies = await page.context().cookies(urls.frontendUrl);
const cookieHeader = authCookies.map(({ name, value }) => `${name}=${value}`).join("; ");
const authHeaders: Record<string, string> = { accept: "application/json" };
if (cookieHeader) authHeaders.cookie = cookieHeader;
const fetchJson = async (path: string): Promise<any> => {
const response = await fetch(new URL(path, urls.frontendUrl), { headers: authHeaders });
return response.json().catch(() => null);
};
const tasksPayload = await fetchJson("/api/code-queue-direct/api/tasks?limit=300&lite=1&devReady=0");
const tasks = Array.isArray(tasksPayload?.tasks) ? tasksPayload.tasks : [];
const prioritized = tasks
.filter((task: any) => ["failed", "canceled", "running", "judging", "succeeded"].includes(String(task?.status || "")))
.concat(tasks);
for (const task of prioritized) {
const taskId = String(task?.id || "");
if (!taskId) continue;
const summaryPayload = await fetchJson(`/api/code-queue-direct/api/tasks/${encodeURIComponent(taskId)}/trace-summary`);
const summary = summaryPayload?.summary || null;
const attempts = Array.isArray(summary?.attempts) ? summary.attempts : [];
const errorAttempt = attempts.find((attempt: any) => Number(attempt?.errorCount ?? 0) > 0) || null;
const errorCount = Number(errorAttempt?.errorCount ?? summary?.errorCount ?? 0);
const attemptIndex = Number(errorAttempt?.index ?? 0);
if (Number.isFinite(errorCount) && errorCount > 0 && Number.isFinite(attemptIndex) && attemptIndex > 0) {
codeQueueErrorHighlightMetrics = {
checked: true,
candidateFound: true,
taskId,
errorCount,
attemptIndex,
status: String(task?.status || summary?.status || ""),
};
break;
}
}
if (!codeQueueErrorHighlightMetrics) {
codeQueueErrorHighlightMetrics = { checked: true, candidateFound: false, taskCount: tasks.length };
}
if (codeQueueErrorHighlightMetrics.candidateFound) {
const errorTaskSearch = page.getByTestId("codex-task-search-input");
if (await errorTaskSearch.count()) {
await errorTaskSearch.fill(codeQueueErrorHighlightMetrics.taskId);
await page.waitForTimeout(1000);
}
for (let index = 0; index < 8; index += 1) {
if (await page.getByTestId(`codex-task-${codeQueueErrorHighlightMetrics.taskId}`).count()) break;
const moreButton = page.getByTestId("codex-load-more-tasks-button");
if (!(await moreButton.count())) break;
await moreButton.scrollIntoViewIfNeeded().catch(() => undefined);
await moreButton.click();
await page.waitForTimeout(500);
}
const errorTaskCard = page.getByTestId(`codex-task-${codeQueueErrorHighlightMetrics.taskId}`);
await errorTaskCard.scrollIntoViewIfNeeded({ timeout: 10000 });
await errorTaskCard.click();
await page.waitForSelector('[data-testid="codex-execution-summary"]', { timeout: 15000 });
const errorSummaryTestId = Number(codeQueueErrorHighlightMetrics.attemptIndex || 0) > 1
? `codex-execution-summary-attempt-${Number(codeQueueErrorHighlightMetrics.attemptIndex)}`
: "codex-execution-summary";
const errorPillTestId = `${errorSummaryTestId}-error-count`;
const errorSummaryLocator = page.getByTestId(errorSummaryTestId);
const errorPillLocator = page.getByTestId(errorPillTestId);
await errorPillLocator.waitFor({ state: "visible", timeout: 30000 });
const collapsedMetrics = await page.evaluate(({ summaryTestId, pillTestId }) => {
const summary = document.querySelector(`[data-testid="${summaryTestId}"]`) as HTMLDetailsElement | null;
const pill = document.querySelector(`[data-testid="${pillTestId}"]`) as HTMLElement | null;
const pillStyle = pill ? getComputedStyle(pill) : null;
const dangerColor = /207,\s*106,\s*84/u;
return {
summaryOpen: Boolean(summary?.open),
pillVisible: Boolean(pill && pill.offsetParent !== null),
pillText: String(pill?.textContent || "").trim(),
pillClass: String(pill?.className || ""),
pillColor: pillStyle?.color || "",
pillBorderColor: pillStyle?.borderColor || "",
pillBackgroundColor: pillStyle?.backgroundColor || "",
hasDangerColor: dangerColor.test(pillStyle?.color || "") && dangerColor.test(pillStyle?.borderColor || ""),
};
}, { summaryTestId: errorSummaryTestId, pillTestId: errorPillTestId });
await errorSummaryLocator.locator("summary").click();
await page.waitForFunction((testId) => (document.querySelector(`[data-testid="${testId}"]`) as HTMLDetailsElement | null)?.open === true, errorSummaryTestId, { timeout: 5000 });
await page.waitForFunction((testId) => document.querySelectorAll(`[data-testid="${testId}"] .codex-trace-step.error`).length > 0, errorSummaryTestId, { timeout: 15000 });
const expandedMetrics = await page.evaluate(({ summaryTestId }) => {
const summary = document.querySelector(`[data-testid="${summaryTestId}"]`) as HTMLDetailsElement | null;
const errorSteps = Array.from(summary?.querySelectorAll(".codex-trace-step.error") || []) as HTMLElement[];
const first = errorSteps[0] || null;
const channel = first?.querySelector(".codex-output-channel") as HTMLElement | null;
const code = first?.querySelector("code") as HTMLElement | null;
const firstStyle = first ? getComputedStyle(first) : null;
const channelStyle = channel ? getComputedStyle(channel) : null;
const codeStyle = code ? getComputedStyle(code) : null;
const dangerColor = /207,\s*106,\s*84/u;
return {
errorStepCount: errorSteps.length,
firstStepClass: String(first?.className || ""),
firstStepBorderColor: firstStyle?.borderColor || "",
firstStepBackgroundColor: firstStyle?.backgroundColor || "",
firstChannelColor: channelStyle?.color || "",
firstCodeColor: codeStyle?.color || "",
hasDangerColor: dangerColor.test(firstStyle?.borderColor || "") && dangerColor.test(channelStyle?.color || "") && dangerColor.test(codeStyle?.color || ""),
};
}, { summaryTestId: errorSummaryTestId });
codeQueueErrorHighlightMetrics = {
...codeQueueErrorHighlightMetrics,
collapsed: collapsedMetrics,
expanded: expandedMetrics,
ok: collapsedMetrics.pillVisible === true
&& collapsedMetrics.pillText.startsWith("Error ")
&& collapsedMetrics.hasDangerColor === true
&& collapsedMetrics.summaryOpen === false
&& expandedMetrics.errorStepCount > 0
&& expandedMetrics.firstStepClass.includes("error")
&& expandedMetrics.hasDangerColor === true,
};
}
}
codeQueueOutputText = await page.locator('[data-testid="codex-output"]').innerText({ timeout: 5000 });
codeQueueText = await page.locator('[data-testid="code-queue-page"]').innerText({ timeout: 5000 });
codeQueueHtmlGuard = await page.evaluate(async () => {
const root = document.getElementById("root");
const overviewAttr = root?.getAttribute("data-codex-overview") || "";
const response = await fetch(window.location.pathname, { credentials: "same-origin" });
const html = await response.text();
return {
rootAttrLength: overviewAttr.length,
rootAttrMissing: overviewAttr.length === 0,
sourceBytes: html.length,
sourceAttrMissing: !html.includes("data-codex-overview"),
sourceNoBasePrompt: !html.includes("basePrompt"),
};
});
codeQueueOptions = await page.locator('[data-testid="code-queue-filter-select"] option').evaluateAll((options) => options.map((option) => (option as HTMLOptionElement).textContent || ""));
codeQueueSubmitQueueControl = await page.evaluate(() => {
const select = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null;
const button = document.querySelector('[data-testid="codex-create-queue-button"]') as HTMLButtonElement | null;
const prompt = document.querySelector('[data-testid="codex-queue-task-form"] textarea') as HTMLTextAreaElement | null;
const prompt = document.querySelector('[data-testid="code-queue-task-form"] textarea') as HTMLTextAreaElement | null;
const provider = document.querySelector('[data-testid="codex-provider-select"]') as HTMLSelectElement | null;
const cwd = document.querySelector('[data-testid="codex-cwd-input"]') as HTMLInputElement | null;
const maxAttempts = document.querySelector('[data-testid="codex-max-attempts-input"]') as HTMLInputElement | null;
const moveSelect = document.querySelector('[data-testid="codex-task-queue-move-select"]') as HTMLSelectElement | null;
const moveButton = document.querySelector('[data-testid="codex-task-queue-move-button"]') as HTMLButtonElement | null;
@@ -1608,22 +1945,25 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
tagName: select?.tagName.toLowerCase() || "",
optionCount: select?.options.length ?? 0,
createButtonVisible: Boolean(button && button.offsetParent !== null),
oldInputMissing: document.querySelector('[data-testid="codex-queue-id-input"]') === null,
oldInputMissing: document.querySelector('[data-testid="code-queue-id-input"]') === null,
promptDefaultEmpty: (prompt?.value || "") === "",
providerValue: provider?.value || "",
providerOptions: Array.from(provider?.options || []).map((option) => ({ value: option.value, text: option.textContent || "" })),
cwdValue: cwd?.value || "",
maxAttemptsMax: maxAttempts?.max || "",
maxAttemptsValue: maxAttempts?.value || "",
moveQueueVisible: Boolean(moveSelect && moveSelect.offsetParent !== null && moveButton && moveButton.offsetParent !== null),
};
});
codexQueuePromptDefaultEmpty = Boolean(codexQueueSubmitQueueControl.promptDefaultEmpty);
codexQueueSwitchMetrics = await page.locator('[data-testid="codex-queue-filter-select"] option').evaluateAll((options) => ({
codeQueuePromptDefaultEmpty = Boolean(codeQueueSubmitQueueControl.promptDefaultEmpty);
codeQueueSwitchMetrics = await page.locator('[data-testid="code-queue-filter-select"] option').evaluateAll((options) => ({
optionCount: options.length,
queueValues: options.map((option) => (option as HTMLOptionElement).value).filter((value) => value !== "__all__"),
switched: false,
}));
if (codexQueueSwitchMetrics.queueValues.length > 0) {
const targetQueueId = String(codexQueueSwitchMetrics.queueValues.find((value: string) => value !== "default") || codexQueueSwitchMetrics.queueValues[0]);
await page.getByTestId("codex-queue-filter-select").selectOption(targetQueueId);
if (codeQueueSwitchMetrics.queueValues.length > 0) {
const targetQueueId = String(codeQueueSwitchMetrics.queueValues.find((value: string) => value !== "default") || codeQueueSwitchMetrics.queueValues[0]);
await page.getByTestId("code-queue-filter-select").selectOption(targetQueueId);
await page.waitForFunction((queueId) => {
const text = document.body.innerText;
return text.includes(`view=${queueId}`) || text.includes(`${queueId} ·`);
@@ -1632,8 +1972,8 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
const cards = Array.from(document.querySelectorAll('[data-testid^="codex-task-codex_"]')).map((node) => (node as HTMLElement).innerText);
return cards.length === 0 || cards.every((text) => text.includes(`queue=${queueId}`));
}, targetQueueId, { timeout: 15000 });
codexQueueSwitchMetrics = { ...codexQueueSwitchMetrics, targetQueueId, switched: true };
await page.getByTestId("codex-queue-filter-select").selectOption("__all__");
codeQueueSwitchMetrics = { ...codeQueueSwitchMetrics, targetQueueId, switched: true };
await page.getByTestId("code-queue-filter-select").selectOption("__all__");
}
}
if (needClaudeqq) {
@@ -1674,19 +2014,19 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
await page.waitForSelector('[data-testid="overview-page"]', { timeout: 10000 });
routeOverviewPath = new URL(page.url()).pathname;
routeOverviewText = await page.locator('[data-testid="overview-page"]').innerText({ timeout: 5000 });
await page.goto(`${urls.frontendUrl}/app/codex-queue/`, { waitUntil: "domcontentloaded", timeout: 15000 });
await page.goto(`${urls.frontendUrl}/app/code-queue/`, { waitUntil: "domcontentloaded", timeout: 15000 });
await page.waitForSelector('[data-testid="app-shell"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="codex-queue-page"]', { timeout: 15000 });
await page.waitForSelector('[data-testid="code-queue-page"]', { timeout: 15000 });
routeCodexPath = new URL(page.url()).pathname;
routeCodexShellMetrics = await page.evaluate(() => ({
appShell: Boolean(document.querySelector('[data-testid="app-shell"]')),
standalone: Boolean(document.querySelector('[data-testid="codex-queue-standalone"]')),
standalone: Boolean(document.querySelector('[data-testid="code-queue-standalone"]')),
railText: document.querySelector(".rail")?.textContent || "",
topbar: Boolean(document.querySelector(".topbar")),
tabsText: document.querySelector(".tabs")?.textContent || "",
codexPage: Boolean(document.querySelector('[data-testid="codex-queue-page"]')),
codexPage: Boolean(document.querySelector('[data-testid="code-queue-page"]')),
}));
await page.locator('.rail button[title="用户服务"]').click();
await page.locator('.rail [role="button"][title="用户服务"]').click();
await page.getByRole("button", { name: /^服务目录$/ }).click();
await page.waitForSelector('[data-testid="microservice-catalog-page"]', { timeout: 10000 });
}
@@ -2050,7 +2390,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
const microserviceCatalogTextLower = microserviceCatalogText.toLowerCase();
const todoNoteTextLower = todoNoteText.toLowerCase();
const findjobTextLower = findjobText.toLowerCase();
const codexQueueTextLower = codexQueueText.toLowerCase();
const codeQueueTextLower = codeQueueText.toLowerCase();
const claudeqqTextLower = claudeqqText.toLowerCase();
const pipelineTextLower = pipelineText.toLowerCase();
const activePipeline = Array.isArray(pipelineSnapshotForFrontend?.pipelines)
@@ -2084,13 +2424,15 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
addSelectedCheck(checks, options, "frontend:gateway-duration-subsecond-visible", gatewayHasSubsecondDuration && !gatewayHasRoundedZeroDuration, { gatewayHasSubsecondDuration, gatewayHasRoundedZeroDuration, gatewayTextPreview: gatewayText.slice(0, 900) });
addSelectedCheck(checks, options, "frontend:provider-operation-availability-visible", sshAvailabilityTexts.length >= 1 && upgradeAvailabilityTexts.length >= 1 && sshAvailabilityTexts.every((text) => text.includes("SSH 透传")) && upgradeAvailabilityTexts.every((text) => text.includes("远程更新")) && upgradeAvailabilityTexts.some((text) => text.includes("always-enabled")), { sshAvailabilityTexts, upgradeAvailabilityTexts });
addSelectedCheck(checks, options, "frontend:overview-pgdata-visible", bodyText.includes("PGDATA") && bodyText.includes(config.database.volume), { bodyPreview: bodyText.slice(0, 800) });
addSelectedCheck(checks, options, "frontend:microservice-catalog-visible", microserviceCatalogTextLower.includes("findjob") && microserviceCatalogTextLower.includes("pipeline") && microserviceCatalogTextLower.includes("todo note") && microserviceCatalogTextLower.includes("met nonlinear") && microserviceCatalogTextLower.includes("claudeqq") && microserviceCatalogTextLower.includes("codex queue") && microserviceCatalogText.includes("D601") && microserviceCatalogText.includes(config.providerGateway.id) && microserviceCatalogTextLower.includes("private") && microserviceCatalogText.includes("https://gitee.com/Lyon1998/findjob") && microserviceCatalogText.includes("https://github.com/pikasTech/pipeline") && microserviceCatalogText.includes("https://github.com/pikasTech/met_nonlinear") && microserviceCatalogText.includes("https://gitee.com/lyon1998/agent_skills") && microserviceCatalogText.includes("https://gitee.com/Lyon1998/todo_note") && microserviceCatalogText.includes("https://github.com/pikasTech/unidesk"), { microserviceCatalogPreview: microserviceCatalogText.slice(0, 2000) });
addSelectedCheck(checks, options, "frontend:microservice-catalog-visible", microserviceCatalogTextLower.includes("findjob") && microserviceCatalogTextLower.includes("pipeline") && microserviceCatalogTextLower.includes("todo note") && microserviceCatalogTextLower.includes("met nonlinear") && microserviceCatalogTextLower.includes("claudeqq") && microserviceCatalogTextLower.includes("code queue") && microserviceCatalogText.includes("D601") && microserviceCatalogText.includes(config.providerGateway.id) && microserviceCatalogTextLower.includes("private") && microserviceCatalogText.includes("https://gitee.com/Lyon1998/findjob") && microserviceCatalogText.includes("https://github.com/pikasTech/pipeline") && microserviceCatalogText.includes("https://github.com/pikasTech/met_nonlinear") && microserviceCatalogText.includes("https://gitee.com/lyon1998/agent_skills") && microserviceCatalogText.includes("https://gitee.com/Lyon1998/todo_note") && microserviceCatalogText.includes("https://github.com/pikasTech/unidesk"), { microserviceCatalogPreview: microserviceCatalogText.slice(0, 2000) });
addSelectedCheck(checks, options, "frontend:todo-note-integrated-visible", todoNoteTextLower.includes("todo note 工作台") && todoNoteText.includes("CONSTAR") && todoNoteText.includes("大论文") && todoNoteText.includes("UI E2E smoke task") && todoNoteText.includes("撤销") && todoNoteText.includes("重做") && todoNoteText.includes("全部展开") && todoNoteText.includes("仅 UniDesk frontend 代理访问"), { todoNoteTextPreview: todoNoteText.slice(0, 1400) });
addSelectedCheck(checks, options, "frontend:findjob-integrated-visible", findjobTextLower.includes("findjob 工作台".toLowerCase()) && findjobText.includes("岗位总量") && findjobText.includes("D601") && findjobText.includes("近期岗位") && findjobText.includes("仅 UniDesk frontend 代理访问") && /岗位总量\s+\d+/.test(findjobText) && /health\s+ok/i.test(findjobText) && /[1-9]\d*\/[1-9]\d*\s+preview/i.test(findjobText), { findjobTextPreview: findjobText.slice(0, 1200) });
addSelectedCheck(checks, options, "frontend:codex-queue-integrated-visible", codexQueueTextLower.includes("codex queue") && codexQueueText.includes("gpt-5.4-mini") && codexQueueText.includes("gpt-5.4") && codexQueueText.includes("gpt-5.5") && codexQueueText.includes("提交任务") && codexQueueText.includes("入队份数") && codexQueueText.includes("追加 prompt") && codexQueueText.includes("打断") && codexQueueTextLower.includes("查看 queue") && codexQueueText.includes("创建 queue") && codexQueueOptions.some((text) => text.includes("All queues")) && codexQueueTracePlacement.firstChildIsTrace === true && codexQueueTracePlacement.noPageTopStatus === true && codexQueueTracePlacement.filterInsideTracePanel === true && codexQueueTracePlacement.traceStatusVisible === true && codexQueueTracePlacement.markAllReadVisible === true && codexQueueGlobalStatus.activeMicroserviceVisible === true && codexQueueSubmitQueueControl.tagName === "select" && codexQueueSubmitQueueControl.createButtonVisible === true && codexQueueSubmitQueueControl.oldInputMissing === true && codexQueueSubmitQueueControl.maxAttemptsMax === "99" && codexQueueSubmitQueueControl.maxAttemptsValue === "99" && codexQueueSubmitQueueControl.moveQueueVisible === true && codexQueuePromptDefaultEmpty === true && codexQueueSubmitGuard.batchRowVisible === true && codexQueueSubmitGuard.checkboxVisible === true && codexQueueSubmitGuard.disabledBeforeConfirm === true && codexQueueSubmitGuard.enabledAfterConfirm === true && codexQueueSubmitGuard.waitElementMissingBeforeSubmit === true && codexQueueScrollbarMetrics.transcriptThin === true && codexQueueScrollbarMetrics.toolHorizontalHidden === true && (codexQueueSwitchMetrics.optionCount <= 1 || codexQueueSwitchMetrics.switched === true) && codexQueueTextLower.includes("attempts") && codexQueueText.includes("仅 UniDesk frontend 代理访问") && (codexQueueTaskCount === 0 || codexQueueOutputText.includes("Submitted prompt")), { codexQueueTaskCount, codexQueueOptions, codexQueueSwitchMetrics, codexQueueSubmitQueueControl, codexQueueSubmitGuard, codexQueueScrollbarMetrics, codexQueuePromptDefaultEmpty, codexQueueTracePlacement, codexQueueGlobalStatus, codexQueueOutputPreview: codexQueueOutputText.slice(0, 900), codexQueueTextPreview: codexQueueText.slice(0, 1400) });
addSelectedCheck(checks, options, "frontend:codex-queue-initial-prompt-full-expand",
codexInitialPromptFullMetrics.candidateFound === false
|| (
addSelectedCheck(checks, options, "frontend:code-queue-integrated-visible", codeQueueTextLower.includes("code queue") && codeQueueText.includes("gpt-5.4-mini") && codeQueueText.includes("gpt-5.4") && codeQueueText.includes("gpt-5.5") && codeQueueText.includes("提交任务") && codeQueueText.includes("执行 Provider") && codeQueueText.includes("入队份数") && codeQueueText.includes("追加 prompt") && codeQueueText.includes("打断") && codeQueueTextLower.includes("查看 queue") && codeQueueText.includes("创建 queue") && codeQueueOptions.some((text) => text.includes("All queues")) && codeQueueTracePlacement.firstChildIsTrace === true && codeQueueTracePlacement.noPageTopStatus === true && codeQueueTracePlacement.filterInsideTracePanel === true && codeQueueTracePlacement.taskSearchVisible === true && codeQueueTracePlacement.traceStatusVisible === true && codeQueueTracePlacement.markAllReadVisible === true && codeQueueGlobalStatus.activeMicroserviceVisible === true && codeQueueSidebarUpdateMetrics.hasRecentUpdateLabel === true && codeQueueHtmlGuard.rootAttrMissing === true && codeQueueHtmlGuard.sourceAttrMissing === true && codeQueueHtmlGuard.sourceNoBasePrompt === true && codeQueueSubmitQueueControl.tagName === "select" && codeQueueSubmitQueueControl.createButtonVisible === true && codeQueueSubmitQueueControl.oldInputMissing === true && codeQueueSubmitQueueControl.providerValue === "main-server" && codeQueueSubmitQueueControl.cwdValue === "/root/unidesk" && Array.isArray(codeQueueSubmitQueueControl.providerOptions) && codeQueueSubmitQueueControl.providerOptions.some((item: any) => item.value === "D601" && String(item.text || "").includes("/home/ubuntu")) && codeQueueSubmitQueueControl.maxAttemptsMax === "99" && codeQueueSubmitQueueControl.maxAttemptsValue === "99" && codeQueueSubmitQueueControl.moveQueueVisible === true && codeQueuePromptDefaultEmpty === true && codeQueueSubmitGuard.batchRowVisible === true && codeQueueSubmitGuard.checkboxVisible === true && codeQueueSubmitGuard.disabledBeforeConfirm === true && codeQueueSubmitGuard.enabledAfterConfirm === true && codeQueueSubmitGuard.waitElementMissingBeforeSubmit === true && codeQueueScrollbarMetrics.transcriptThin === true && codeQueueScrollbarMetrics.toolHorizontalHidden === true && (codeQueueSwitchMetrics.optionCount <= 1 || codeQueueSwitchMetrics.switched === true) && codeQueueTextLower.includes("attempts") && codeQueueText.includes("仅 UniDesk frontend 代理访问") && (codeQueueTaskCount === 0 || codeQueueOutputText.includes("Submitted prompt")), { codeQueueTaskCount, codeQueueOptions, codeQueueSwitchMetrics, codeQueueSubmitQueueControl, codeQueueSubmitGuard, codeQueueScrollbarMetrics, codeQueuePromptDefaultEmpty, codeQueueTracePlacement, codeQueueGlobalStatus, codeQueueSidebarUpdateMetrics, codeQueueHtmlGuard, codeQueueOutputPreview: codeQueueOutputText.slice(0, 900), codeQueueTextPreview: codeQueueText.slice(0, 1400) });
addSelectedCheck(checks, options, "frontend:code-queue-summary-mobile-wrap", codeQueueSummaryMobileMetrics.checked === true && (codeQueueSummaryMobileMetrics.summaryCount === 0 || codeQueueSummaryMobileMetrics.ok === true), { codeQueueSummaryMobileMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-error-red-markers", codeQueueErrorHighlightMetrics.checked === true && codeQueueErrorHighlightMetrics.candidateFound === true && codeQueueErrorHighlightMetrics.ok === true, { codeQueueErrorHighlightMetrics });
addSelectedCheck(checks, options, "frontend:code-queue-initial-prompt-full-expand",
codexInitialPromptFullMetrics.candidateFound === false
|| (
codexInitialPromptFullMetrics.promptChars > codexInitialPromptFullMetrics.displayPromptChars
&& codexInitialPromptFullMetrics.initialDefaultOpen === false
&& codexInitialPromptFullMetrics.initialExpanded === true
@@ -2099,7 +2441,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& codexInitialPromptFullMetrics.legacyPromptPanelMissing === true
),
{ codexInitialPromptFullMetrics });
addSelectedCheck(checks, options, "frontend:codex-queue-trace-full-load",
addSelectedCheck(checks, options, "frontend:code-queue-trace-full-load",
codexTraceFullMetrics.candidateFound === false
|| (
codexTraceFullMetrics.apiTotal >= 20
@@ -2113,11 +2455,11 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& codexTraceFullMetrics.loadPartial !== "true"
),
{ codexTraceFullMetrics });
addSelectedCheck(checks, options, "frontend:codex-queue-judge-wrap",
addSelectedCheck(checks, options, "frontend:code-queue-judge-wrap",
codexJudgeWrapMetrics.checked === true && codexJudgeWrapMetrics.ok === true,
{ codexJudgeWrapMetrics });
addSelectedCheck(checks, options, "frontend:claudeqq-integrated-visible", claudeqqTextLower.includes("claudeqq 工作台") && claudeqqText.includes("D601") && claudeqqText.includes("QQ 事件订阅") && claudeqqText.includes("消息推送") && claudeqqText.includes("事件缓存") && claudeqqText.includes("主用户私聊账号") && claudeqqText.includes("645275593") && claudeqqTextLower.includes("napcat 容器登录") && (claudeqqText.includes("二维码") || claudeqqText.includes("QR SOURCE") || claudeqqText.includes("QR Source") || claudeqqText.includes("已登录")) && claudeqqText.includes("仅 UniDesk frontend 代理访问") && !claudeqqText.includes("{\n"), { claudeqqTextPreview: claudeqqText.slice(0, 1400) });
addSelectedCheck(checks, options, "frontend:url-route-deeplink", routeInitialPath === "/app/pipeline/" && routeDockerPath === "/nodes/docker/" && routeBackPath === "/app/pipeline/" && routeOverviewPath === "/ops/status/" && routeCodexPath === "/app/codex-queue/" && routeDeepLinkText.toLowerCase().includes("pipeline v2 工作台".toLowerCase()) && routeOverviewText.includes("核心指标") && routeCodexShellMetrics.appShell === true && routeCodexShellMetrics.standalone === false && routeCodexShellMetrics.topbar === true && routeCodexShellMetrics.codexPage === true && String(routeCodexShellMetrics.railText || "").includes("用户服务") && String(routeCodexShellMetrics.tabsText || "").includes("Codex Queue"), { routeInitialPath, routeDockerPath, routeBackIntermediatePath, routeBackPath, routeOverviewPath, routeCodexPath, routeCodexShellMetrics, routeDeepLinkPreview: routeDeepLinkText.slice(0, 1200), routeOverviewPreview: routeOverviewText.slice(0, 800) });
addSelectedCheck(checks, options, "frontend:url-route-deeplink", routeInitialPath === "/app/pipeline/" && routeDockerPath === "/nodes/docker/" && routeBackPath === "/app/pipeline/" && routeOverviewPath === "/ops/status/" && routeCodexPath === "/app/code-queue/" && routeDeepLinkText.toLowerCase().includes("pipeline v2 工作台".toLowerCase()) && routeOverviewText.includes("核心指标") && routeCodexShellMetrics.appShell === true && routeCodexShellMetrics.standalone === false && routeCodexShellMetrics.topbar === true && routeCodexShellMetrics.codexPage === true && String(routeCodexShellMetrics.railText || "").includes("用户服务") && String(routeCodexShellMetrics.tabsText || "").includes("Code Queue"), { routeInitialPath, routeDockerPath, routeBackIntermediatePath, routeBackPath, routeOverviewPath, routeCodexPath, routeCodexShellMetrics, routeDeepLinkPreview: routeDeepLinkText.slice(0, 1200), routeOverviewPreview: routeOverviewText.slice(0, 800) });
addSelectedCheck(checks, options, "frontend:pipeline-integrated-visible",
pipelineTextLower.includes("pipeline v2 工作台".toLowerCase())
&& pipelineText.includes("D601")
@@ -2194,7 +2536,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
{ pipelineObservationGanttMetrics });
addSelectedCheck(checks, options, "frontend:pipeline-step-timeline-visible",
pipelineStepTimelineText.includes("OpenCode Trace")
&& pipelineStepTimelineText.includes("Codex Queue")
&& pipelineStepTimelineText.includes("Code Queue")
&& pipelineStepTimelineText.toLowerCase().includes("tools")
&& pipelineStepTimelineText.includes("Trace")
&& !firstPipelineStepSummaryText.includes("{\n")