feat: add unified OA event flow

This commit is contained in:
Codex
2026-05-14 09:41:55 +00:00
parent b36d7f94d7
commit 9f483b002c
37 changed files with 3417 additions and 502 deletions
+110 -5
View File
@@ -38,6 +38,7 @@ const NETWORK_CHECK_NAMES = [
"network:claudeqq-public-blocked",
"network:todo-note-public-blocked",
"network:code-queue-public-blocked",
"network:oa-event-flow-public-blocked",
"network:filebrowser-public-blocked",
] as const;
@@ -58,6 +59,7 @@ const SERVICE_CHECK_NAMES = [
"microservice:catalog-met-nonlinear",
"microservice:catalog-claudeqq",
"microservice:catalog-todo-note",
"microservice:catalog-oa-event-flow",
"microservice:catalog-code-queue",
"microservice:catalog-filebrowser",
"microservice:filebrowser-health",
@@ -88,6 +90,12 @@ const SERVICE_CHECK_NAMES = [
"microservice:code-queue-status",
"microservice:code-queue-health",
"microservice:code-queue-tasks",
"microservice:oa-event-flow-status",
"microservice:oa-event-flow-health",
"microservice:oa-event-flow-diagnostics",
"microservice:oa-event-flow-events",
"microservice:oa-event-flow-pipeline-bridge",
"microservice:oa-event-flow-stats",
] as const;
const DATABASE_CHECK_NAMES = [
@@ -118,6 +126,7 @@ const FRONTEND_CHECK_NAMES = [
"frontend:microservice-catalog-visible",
"frontend:todo-note-integrated-visible",
"frontend:findjob-integrated-visible",
"frontend:oa-event-flow-visible",
"frontend:code-queue-integrated-visible",
"frontend:code-queue-enqueue-await-smoke",
"frontend:code-queue-summary-mobile-wrap",
@@ -309,6 +318,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/oa-event-flow/": "oa-event-flow-page",
"/app/code-queue/": "code-queue-page",
};
@@ -935,6 +945,7 @@ async function exposureChecks(config: UniDeskConfig, urls: PublicUrls, checks: E
const claudeqqPublic = await fetchProbe(`http://${config.network.publicHost}:3290/health`, 2500);
const todoNotePublic = await fetchProbe(`http://${config.network.publicHost}:4211/api/health`, 2500);
const codeQueuePublic = await fetchProbe(`http://${config.network.publicHost}:14222/health`, 2500);
const oaEventFlowPublic = await fetchProbe(`http://${config.network.publicHost}:4255/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);
@@ -944,6 +955,7 @@ async function exposureChecks(config: UniDeskConfig, urls: PublicUrls, checks: E
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:code-queue-public-blocked", (codeQueuePublic as { reachable?: boolean }).reachable === false, codeQueuePublic);
addSelectedCheck(checks, options, "network:oa-event-flow-public-blocked", (oaEventFlowPublic as { reachable?: boolean }).reachable === false, oaEventFlowPublic);
addSelectedCheck(checks, options, "network:filebrowser-public-blocked", (filebrowserPublic as { reachable?: boolean }).reachable === false, filebrowserPublic);
}
@@ -975,6 +987,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 oaEventFlowStatus = dockerCoreJson("/api/microservices/oa-event-flow/status");
const oaEventFlowHealth = dockerCoreJson("/api/microservices/oa-event-flow/health");
const oaEventFlowDiagnostics = dockerCoreJson("/api/microservices/oa-event-flow/proxy/api/diagnostics");
const oaEventFlowEvents = dockerCoreJson("/api/microservices/oa-event-flow/proxy/api/events?limit=10");
const oaEventFlowPipelineEvents = dockerCoreJson("/api/microservices/oa-event-flow/proxy/api/events?tags=service:pipeline&limit=10");
const oaEventFlowStats = dockerCoreJson("/api/microservices/oa-event-flow/proxy/api/stats/trace?limit=10");
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");
@@ -1026,6 +1044,7 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
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 oaEventFlow = microserviceList.find((service) => service.id === "oa-event-flow");
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");
@@ -1043,6 +1062,11 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
const claudeqqNapcatLoginBody = (claudeqqNapcatLogin as { body?: { ok?: boolean; napcat?: { containerized?: boolean; connected?: boolean; httpConnected?: boolean; wsConnected?: 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 oaEventFlowHealthBody = (oaEventFlowHealth as { body?: { ok?: boolean; service?: string; databaseReady?: boolean; eventCount?: number; traceStatsCount?: number; latestSequence?: number; pipelineBridge?: { enabled?: boolean; inFlight?: boolean; lastFinishedAt?: string; lastError?: string; insertedCount?: number; duplicateCount?: number } } }).body;
const oaEventFlowDiagnosticsBody = (oaEventFlowDiagnostics as { body?: { ok?: boolean; service?: string; databaseReady?: boolean; eventCount?: number; traceStatsCount?: number; latestSequence?: number; eventTypes?: unknown[]; recentEvents?: unknown[]; recentTraceStats?: unknown[]; pipelineBridge?: { enabled?: boolean; lastFinishedAt?: string; lastError?: string; insertedCount?: number; duplicateCount?: number; runs?: Record<string, unknown> } } }).body;
const oaEventFlowEventsBody = (oaEventFlowEvents as { body?: { ok?: boolean; events?: unknown[]; returned?: number } }).body;
const oaEventFlowPipelineEventsBody = (oaEventFlowPipelineEvents as { body?: { ok?: boolean; events?: Array<{ tags?: unknown[]; sourceId?: string; type?: string; payload?: { runId?: string; pipelineId?: string } }>; returned?: number } }).body;
const oaEventFlowStatsBody = (oaEventFlowStats as { body?: { ok?: boolean; stats?: unknown[]; returned?: number } }).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;
@@ -1074,6 +1098,7 @@ 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-oa-event-flow", (microservices as { ok?: boolean }).ok === true && oaEventFlow?.providerId === config.providerGateway.id && oaEventFlow.backend?.public === false && oaEventFlow.runtime?.container?.name === "oa-event-flow-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"
@@ -1126,6 +1151,22 @@ 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:oa-event-flow-status", (oaEventFlowStatus as { ok?: boolean }).ok === true && (oaEventFlowStatus as { body?: { microservice?: { id?: string; providerId?: string } } }).body?.microservice?.providerId === config.providerGateway.id, oaEventFlowStatus);
addSelectedCheck(checks, options, "microservice:oa-event-flow-health", (oaEventFlowHealth as { ok?: boolean }).ok === true && oaEventFlowHealthBody?.ok === true && oaEventFlowHealthBody.service === "oa-event-flow" && oaEventFlowHealthBody.databaseReady === true, oaEventFlowHealth);
addSelectedCheck(checks, options, "microservice:oa-event-flow-diagnostics", (oaEventFlowDiagnostics as { ok?: boolean }).ok === true && oaEventFlowDiagnosticsBody?.ok === true && oaEventFlowDiagnosticsBody.service === "oa-event-flow" && oaEventFlowDiagnosticsBody.databaseReady === true && Number.isFinite(oaEventFlowDiagnosticsBody.eventCount) && Array.isArray(oaEventFlowDiagnosticsBody.eventTypes), oaEventFlowDiagnostics);
addSelectedCheck(checks, options, "microservice:oa-event-flow-events", (oaEventFlowEvents as { ok?: boolean }).ok === true && oaEventFlowEventsBody?.ok === true && Array.isArray(oaEventFlowEventsBody.events), oaEventFlowEvents);
addSelectedCheck(checks, options, "microservice:oa-event-flow-pipeline-bridge",
(oaEventFlowPipelineEvents as { ok?: boolean }).ok === true
&& oaEventFlowPipelineEventsBody?.ok === true
&& Array.isArray(oaEventFlowPipelineEventsBody.events)
&& oaEventFlowPipelineEventsBody.events.some((event) => Array.isArray(event.tags) && event.tags.includes("service:pipeline") && event.tags.some((tag) => typeof tag === "string" && tag.startsWith("epoch:")))
&& oaEventFlowDiagnosticsBody?.pipelineBridge?.enabled === true
&& String(oaEventFlowDiagnosticsBody.pipelineBridge.lastError || "").length === 0,
{
pipelineBridge: oaEventFlowDiagnosticsBody?.pipelineBridge,
sampleEvents: oaEventFlowPipelineEventsBody?.events?.slice(0, 5),
});
addSelectedCheck(checks, options, "microservice:oa-event-flow-stats", (oaEventFlowStats as { ok?: boolean }).ok === true && oaEventFlowStatsBody?.ok === true && Array.isArray(oaEventFlowStatsBody.stats), oaEventFlowStats);
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);
@@ -1226,6 +1267,7 @@ 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 needOaEventFlow = wants("frontend:oa-event-flow-visible");
const needCodeQueue = wantsAny([
"frontend:code-queue-integrated-visible",
"frontend:code-queue-enqueue-await-smoke",
@@ -1309,6 +1351,8 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
let microserviceCatalogText = "";
let todoNoteText = "";
let findjobText = "";
let oaEventFlowText = "";
let oaEventFlowMetrics: any = { pageVisible: false, eventTableVisible: false, statsVisible: false, tagFilterValue: "", rawButtonCount: 0 };
let codeQueueText = "";
let codeQueueOutputText = "";
let codeQueueTaskCount = 0;
@@ -1524,9 +1568,9 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
}
}
if (needMicroserviceCatalog || needTodoNote || needFindJob || needCodeQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
if (needMicroserviceCatalog || needTodoNote || needFindJob || needOaEventFlow || needCodeQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
await page.getByRole("button", { name: /用户服务/ }).click();
if (needMicroserviceCatalog || needTodoNote || needFindJob || needCodeQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
if (needMicroserviceCatalog || needTodoNote || needFindJob || needOaEventFlow || needCodeQueue || needClaudeqq || needRouteDeepLink || needPipeline || needMetNonlinear) {
await page.waitForSelector('[data-testid="microservice-catalog-page"]', { timeout: 10000 });
}
if (needMicroserviceCatalog) {
@@ -1535,6 +1579,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-oa-event-flow"]', { 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 });
}
@@ -1591,8 +1636,29 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
}, undefined, { timeout: 30000 });
findjobText = await page.locator('[data-testid="findjob-page"]').innerText({ timeout: 5000 });
}
if (needOaEventFlow) {
await page.getByRole("button", { name: /OA Event Flow/ }).click();
await page.waitForSelector('[data-testid="oa-event-flow-page"]', { timeout: 10000 });
await page.waitForSelector('[data-testid="oa-event-flow-event-table"]', { timeout: 30000 });
await page.waitForSelector('[data-testid="oa-event-flow-stats"]', { timeout: 30000 });
oaEventFlowText = await page.locator('[data-testid="oa-event-flow-page"]').innerText({ timeout: 5000 });
oaEventFlowMetrics = await page.evaluate(() => {
const pageRoot = document.querySelector('[data-testid="oa-event-flow-page"]') as HTMLElement | null;
const eventTable = document.querySelector('[data-testid="oa-event-flow-event-table"]') as HTMLElement | null;
const stats = document.querySelector('[data-testid="oa-event-flow-stats"]') as HTMLElement | null;
const filter = document.querySelector('[data-testid="oa-event-flow-tag-filter"]') as HTMLInputElement | null;
return {
pageVisible: Boolean(pageRoot),
eventTableVisible: Boolean(eventTable),
statsVisible: Boolean(stats),
tagFilterValue: filter?.value || "",
rawButtonCount: pageRoot?.querySelectorAll('[data-testid^="raw-oa-"]').length ?? 0,
textPreview: pageRoot?.innerText.slice(0, 800) || "",
};
});
}
if (needCodeQueue) {
await page.getByRole("button", { name: /Code Queue/ }).click();
await page.getByLabel("用户服务 子功能").getByRole("button", { name: "Code Queue" }).click();
await page.waitForSelector('[data-testid="code-queue-page"]', { timeout: 10000 });
await page.waitForFunction(() => {
const text = document.body.innerText;
@@ -1638,6 +1704,9 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
optionCount: select?.options.length ?? 0,
createButtonVisible: Boolean(button && button.offsetParent !== null),
mergeButtonVisible: Boolean(mergeButton && mergeButton.offsetParent !== null),
mergeButtonDisabled: Boolean(mergeButton?.disabled),
mergeSourceInlineMissing: document.querySelector('[data-testid="code-queue-task-form"] [data-testid="codex-merge-source-queue-select"]') === null,
mergeDialogMissingBeforeClick: document.querySelector('[data-testid="codex-merge-queue-dialog"]') === null,
oldInputMissing: document.querySelector('[data-testid="code-queue-id-input"]') === null,
promptDefaultEmpty: (prompt?.value || "") === "",
providerValue: provider?.value || "",
@@ -2101,6 +2170,9 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
optionCount: select?.options.length ?? 0,
createButtonVisible: Boolean(button && button.offsetParent !== null),
mergeButtonVisible: Boolean(mergeButton && mergeButton.offsetParent !== null),
mergeButtonDisabled: Boolean(mergeButton?.disabled),
mergeSourceInlineMissing: document.querySelector('[data-testid="code-queue-task-form"] [data-testid="codex-merge-source-queue-select"]') === null,
mergeDialogMissingBeforeClick: document.querySelector('[data-testid="codex-merge-queue-dialog"]') === null,
oldInputMissing: document.querySelector('[data-testid="code-queue-id-input"]') === null,
promptDefaultEmpty: (prompt?.value || "") === "",
providerValue: provider?.value || "",
@@ -2112,6 +2184,26 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
};
});
codeQueuePromptDefaultEmpty = Boolean(codeQueueSubmitQueueControl.promptDefaultEmpty);
if (codeQueueSubmitQueueControl.mergeButtonVisible && !codeQueueSubmitQueueControl.mergeButtonDisabled) {
await page.getByTestId("codex-merge-queue-button").click();
await page.waitForSelector('[data-testid="codex-merge-queue-dialog"]', { timeout: 5000 });
const mergeDialogMetrics = await page.evaluate(() => {
const dialog = document.querySelector('[data-testid="codex-merge-queue-dialog"]') as HTMLElement | null;
const select = document.querySelector('[data-testid="codex-merge-queue-dialog"] [data-testid="codex-merge-source-queue-select"]') as HTMLSelectElement | null;
const text = dialog?.textContent || "";
return {
mergeDialogVisible: Boolean(dialog && dialog.offsetParent !== null),
mergeDialogSelectVisible: Boolean(select && select.offsetParent !== null),
mergeDialogSourceOptionCount: select?.options.length ?? 0,
mergeDialogSelectInsideSubmitForm: Boolean(document.querySelector('[data-testid="code-queue-task-form"] [data-testid="codex-merge-source-queue-select"]')),
mergeDialogUsesCommonComponent: Boolean(dialog?.classList.contains("unidesk-dialog")),
mergeDialogDeleteNoteVisible: text.includes("自动删除源 queue"),
};
});
codeQueueSubmitQueueControl = { ...codeQueueSubmitQueueControl, ...mergeDialogMetrics };
await page.getByTestId("codex-merge-queue-cancel").click();
await page.waitForSelector('[data-testid="codex-merge-queue-dialog"]', { state: "detached", timeout: 5000 }).catch(() => undefined);
}
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__"),
@@ -2580,10 +2672,23 @@ 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("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:microservice-catalog-visible", microserviceCatalogTextLower.includes("findjob") && microserviceCatalogTextLower.includes("pipeline") && microserviceCatalogTextLower.includes("todo note") && microserviceCatalogTextLower.includes("met nonlinear") && microserviceCatalogTextLower.includes("claudeqq") && microserviceCatalogTextLower.includes("oa event flow") && 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: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") && 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.mergeButtonVisible === 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:oa-event-flow-visible",
oaEventFlowText.toLowerCase().includes("oa event flow")
&& oaEventFlowText.includes("事件表")
&& oaEventFlowText.includes("统计中心")
&& oaEventFlowText.includes("service:code-queue")
&& oaEventFlowText.includes("仅 UniDesk frontend 代理访问")
&& oaEventFlowMetrics.pageVisible === true
&& oaEventFlowMetrics.eventTableVisible === true
&& oaEventFlowMetrics.statsVisible === true
&& oaEventFlowMetrics.tagFilterValue === "service:code-queue"
&& Number(oaEventFlowMetrics.rawButtonCount || 0) >= 2
&& !oaEventFlowText.includes("{\n"),
{ oaEventFlowMetrics, oaEventFlowTextPreview: oaEventFlowText.slice(0, 1400) });
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") && 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.mergeButtonVisible === true && codeQueueSubmitQueueControl.mergeSourceInlineMissing === true && codeQueueSubmitQueueControl.mergeDialogMissingBeforeClick === true && (codeQueueSubmitQueueControl.mergeButtonDisabled === true || (codeQueueSubmitQueueControl.mergeDialogVisible === true && codeQueueSubmitQueueControl.mergeDialogSelectVisible === true && Number(codeQueueSubmitQueueControl.mergeDialogSourceOptionCount || 0) > 1 && codeQueueSubmitQueueControl.mergeDialogSelectInsideSubmitForm !== true && codeQueueSubmitQueueControl.mergeDialogUsesCommonComponent === true && codeQueueSubmitQueueControl.mergeDialogDeleteNoteVisible === 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-enqueue-await-smoke",
codeQueueEnqueueAwaitSmoke.checked === true
&& codeQueueEnqueueAwaitSmoke.delayedPostCount === 1