feat: add code queue services and baidu netdisk
This commit is contained in:
+469
-127
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user