From 1e0140fdcd24c27c028cc91ef0ed2244d6904833 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 17 May 2026 01:22:33 +0000 Subject: [PATCH] fix code queue enqueue view preservation --- scripts/src/e2e.ts | 57 +++++++++++- src/components/frontend/src/code-queue.tsx | 100 ++++++++++++++++++++- 2 files changed, 152 insertions(+), 5 deletions(-) diff --git a/scripts/src/e2e.ts b/scripts/src/e2e.ts index 90a10354..628bb160 100644 --- a/scripts/src/e2e.ts +++ b/scripts/src/e2e.ts @@ -564,12 +564,14 @@ function isCodeQueueTaskEnqueueRequest(url: string, method: string): boolean { async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { const marker = `e2e-await-enqueue-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; + const submitQueueId = "e2e-submit-smoke"; const prompt = [ `Code Queue await enqueue smoke ${marker}`, "", "This task is created by the frontend E2E smoke test to verify that the enqueue submit path awaits the backend response before unlocking the form.", ].join("\n"); let delayedPostCount = 0; + const taskOverviewRequests: string[] = []; const routePattern = "**/api/microservices/code-queue/proxy/api/tasks**"; const routeHandler = async (route: any, request: any): Promise => { if (!isCodeQueueTaskEnqueueRequest(request.url(), request.method())) { @@ -580,11 +582,39 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { await new Promise((resolve) => setTimeout(resolve, 900)); await route.continue(); }; + const onRequest = (request: any): void => { + try { + const parsed = new URL(request.url()); + if (request.method() === "GET" && parsed.pathname === "/api/microservices/code-queue/proxy/api/tasks/overview") { + taskOverviewRequests.push(parsed.search); + } + } catch { + // ignore non-URL requests + } + }; + page.on("request", onRequest); await page.route(routePattern, routeHandler); try { await page.getByTestId("code-queue-filter-select").selectOption("__all__").catch(() => undefined); - await page.getByTestId("code-queue-id-select").selectOption("default").catch(() => undefined); + page.once("dialog", (dialog) => { void dialog.accept(submitQueueId); }); + await page.getByTestId("codex-create-queue-button").click(); + await page.waitForFunction((queueId) => { + const select = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null; + const filter = document.querySelector('[data-testid="code-queue-filter-select"]') as HTMLSelectElement | null; + return select?.value === queueId && filter?.value === queueId && Array.from(select?.options || []).some((option) => option.value === queueId); + }, submitQueueId, { timeout: 10000 }); + await page.getByTestId("code-queue-filter-select").selectOption("__all__").catch(() => undefined); + await page.getByTestId("code-queue-id-select").selectOption(submitQueueId); + taskOverviewRequests.length = 0; + const beforeSubmitView = await page.evaluate(() => { + const filter = document.querySelector('[data-testid="code-queue-filter-select"]') as HTMLSelectElement | null; + const submitQueue = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null; + return { + filterValue: filter?.value || "", + submitQueueValue: submitQueue?.value || "", + }; + }); await page.getByTestId("codex-max-attempts-input").fill("1"); await page.getByTestId("codex-repeat-count-input").fill("1"); await page.locator('[data-testid="code-queue-task-form"] textarea').fill(prompt); @@ -626,6 +656,10 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { const textarea = document.querySelector('[data-testid="code-queue-task-form"] textarea') as HTMLTextAreaElement | null; const notice = document.querySelector('[data-testid="codex-create-success"]') as HTMLElement | null; const card = document.querySelector(`[data-testid="codex-task-${CSS.escape(String(id))}"]`) as HTMLElement | null; + const filter = document.querySelector('[data-testid="code-queue-filter-select"]') as HTMLSelectElement | null; + const submitQueue = document.querySelector('[data-testid="code-queue-id-select"]') as HTMLSelectElement | null; + const sessionPanel = document.querySelector('.codex-output-panel') as HTMLElement | null; + const cards = Array.from(document.querySelectorAll('[data-testid^="codex-task-codex_"]')).map((node) => (node as HTMLElement).innerText); return { formBusy: form?.getAttribute("aria-busy") === "true", waitMissing: wait === null, @@ -634,6 +668,10 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { textareaEmpty: (textarea?.value || "") === "", noticeText: notice?.textContent || "", cardVisible: Boolean(card && card.offsetParent !== null), + filterValue: filter?.value || "", + submitQueueValue: submitQueue?.value || "", + selectedTraceHasTask: Boolean(sessionPanel && (sessionPanel.textContent || "").includes(String(id))), + allVisibleCardsRespectFilter: filter?.value === "__all__" || cards.every((text) => text.includes(`queue=${filter?.value || ""}`)), }; }, taskId); const storedTask = await page.evaluate(async (id) => { @@ -663,11 +701,15 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { } })(); await page.getByTestId("codex-max-attempts-input").fill("99").catch(() => undefined); + const afterSubmitOverviewRequests = taskOverviewRequests.slice(); return { checked: true, marker, + submitQueueId, delayedPostCount, requestBody, + afterSubmitOverviewRequests, + postSubmitSubmitQueueOverviewRequestCount: afterSubmitOverviewRequests.filter((search) => search.includes(`queueId=${encodeURIComponent(submitQueueId)}`)).length, responseStatus: response.status(), responseOk: response.ok(), responseBody: { @@ -675,6 +717,7 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { taskIds: Array.isArray(responseBody?.tasks) ? responseBody.tasks.map((task: any) => String(task?.id || "")).filter(Boolean) : [], }, taskId, + beforeSubmitView, duringAwait, afterAwait, storedTask: { @@ -695,6 +738,7 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise { }; } finally { await page.unroute(routePattern, routeHandler).catch(() => undefined); + page.off("request", onRequest); } } @@ -2800,6 +2844,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2 && codeQueueEnqueueAwaitSmoke.responseOk === true && codeQueueEnqueueAwaitSmoke.responseStatus === 202 && /^codex_\d+_[A-Za-z0-9_-]+$/u.test(String(codeQueueEnqueueAwaitSmoke.taskId || "")) + && codeQueueEnqueueAwaitSmoke.submitQueueId === "e2e-submit-smoke" && codeQueueEnqueueAwaitSmoke.duringAwait?.formBusy === true && codeQueueEnqueueAwaitSmoke.duringAwait?.waitVisible === true && codeQueueEnqueueAwaitSmoke.duringAwait?.buttonDisabled === true @@ -2807,9 +2852,17 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2 && codeQueueEnqueueAwaitSmoke.afterAwait?.formBusy === false && codeQueueEnqueueAwaitSmoke.afterAwait?.waitMissing === true && codeQueueEnqueueAwaitSmoke.afterAwait?.textareaEmpty === true + && codeQueueEnqueueAwaitSmoke.beforeSubmitView?.filterValue === "__all__" + && codeQueueEnqueueAwaitSmoke.afterAwait?.filterValue === codeQueueEnqueueAwaitSmoke.beforeSubmitView?.filterValue + && codeQueueEnqueueAwaitSmoke.afterAwait?.submitQueueValue === codeQueueEnqueueAwaitSmoke.beforeSubmitView?.submitQueueValue + && codeQueueEnqueueAwaitSmoke.beforeSubmitView?.submitQueueValue === codeQueueEnqueueAwaitSmoke.submitQueueId + && Number(codeQueueEnqueueAwaitSmoke.postSubmitSubmitQueueOverviewRequestCount || 0) === 0 + && codeQueueEnqueueAwaitSmoke.afterAwait?.cardVisible === true + && codeQueueEnqueueAwaitSmoke.afterAwait?.selectedTraceHasTask === true + && codeQueueEnqueueAwaitSmoke.afterAwait?.allVisibleCardsRespectFilter === true && codeQueueEnqueueAwaitSmoke.storedTask?.ok === true && codeQueueEnqueueAwaitSmoke.storedTask?.id === codeQueueEnqueueAwaitSmoke.taskId - && codeQueueEnqueueAwaitSmoke.storedTask?.queueId === "default" + && codeQueueEnqueueAwaitSmoke.storedTask?.queueId === codeQueueEnqueueAwaitSmoke.submitQueueId && codeQueueEnqueueAwaitSmoke.storedTask?.promptIncludesMarker === true && (codeQueueEnqueueAwaitSmoke.interrupt?.ok === true || codeQueueEnqueueAwaitSmoke.interrupt?.status === 409), { codeQueueEnqueueAwaitSmoke }); diff --git a/src/components/frontend/src/code-queue.tsx b/src/components/frontend/src/code-queue.tsx index e20dae5b..6476653b 100644 --- a/src/components/frontend/src/code-queue.tsx +++ b/src/components/frontend/src/code-queue.tsx @@ -2215,6 +2215,99 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi }; } + function taskVisibleInQueueFilter(task: any, queueFilterId: string): boolean { + return isAllQueues(queueFilterId) || taskQueueLabel(task) === queueFilterId; + } + + function taskMatchesCurrentSearch(task: any): boolean { + const query = normalizedSearchQuery.toLowerCase(); + if (query.length === 0) return true; + const haystack = [ + task?.id, + task?.status, + task?.queueId, + task?.providerId, + task?.model, + task?.cwd, + task?.displayPrompt, + task?.basePrompt, + task?.prompt, + task?.finalResponse, + task?.lastError?.message, + ].map((value) => String(value || "").toLowerCase()).join("\n"); + return haystack.includes(query); + } + + function mergeCreatedTasksIntoCurrentView(createdTasks: any[], queuePatch: any): void { + const rows = createdTasks.filter((task) => String(task?.id || "").length > 0); + if (rows.length === 0 && !queuePatch) return; + const firstTask = rows[0] || null; + const firstId = String(firstTask?.id || ""); + const filteredRows = rows.filter((task) => taskVisibleInQueueFilter(task, selectedQueueId)); + const searchableRows = filteredRows.filter(taskMatchesCurrentSearch); + const activeSortId = String(queuePatch?.activeTaskId || activeTaskIds(queuePatch)[0] || firstId || activeTaskId || ""); + for (const task of rows) { + const taskId = String(task?.id || ""); + if (!taskId) continue; + const transcript = Array.isArray(task?.transcript) ? task.transcript : []; + sessionCacheRef.current.set(taskId, { + ...(sessionCacheRef.current.get(taskId) || {}), + task: { + ...task, + _summaryLoaded: true, + _detailLoaded: transcript.length > 0, + _transcriptComplete: false, + _transcriptPreview: false, + }, + maxSeq: transcriptMaxSeq(transcript), + complete: false, + completeUpdatedAt: "", + }); + } + setTasksData((previous: any) => { + if (!previous && (filteredRows.length === 0 || !queuePatch)) return previous; + const previousRows = taskRows(previous); + const mergedRows = applyLocalReadStateToRows(mergeTaskRowsPreferLatest([previousRows, filteredRows], activeSortId)); + return { + ...(previous || {}), + queue: queuePatch || previous?.queue, + tasks: mergedRows, + pagination: previous?.pagination ? { ...taskPagination(previous), returned: mergedRows.length } : previous?.pagination, + }; + }); + if (searchActive) { + setSearchTasksData((previous: any) => { + if (!previous || searchableRows.length === 0) return previous; + const mergedRows = applyLocalReadStateToRows(mergeTaskRowsPreferLatest([taskRows(previous), searchableRows], activeSortId)); + return { + ...previous, + queue: queuePatch || previous.queue, + tasks: mergedRows, + pagination: previous.pagination ? { ...taskPagination(previous), returned: mergedRows.length } : previous.pagination, + }; + }); + } + if (firstTask && taskVisibleInQueueFilter(firstTask, selectedQueueId) && taskMatchesCurrentSearch(firstTask)) { + detailLoadTokenRef.current += 1; + selectedIdRef.current = firstId; + setSelectedId(firstId); + setSelectedTask(sessionCacheRef.current.get(firstId)?.task || firstTask); + setSelectedDetailLoading(false); + setLoadStats({ + phase: "complete", + taskId: firstId, + queueMs: 0, + detailMs: 0, + totalMs: 0, + chunks: 1, + transcriptRows: Array.isArray(firstTask?.transcript) ? firstTask.transcript.length : 0, + partial: true, + completedAt: new Date(), + }); + } + setRefreshedAt(new Date()); + } + function changeSubmitProvider(nextProviderId: string): void { const next = String(nextProviderId || queue?.mainProviderId || "D601").trim() || "D601"; setProviderId(next); @@ -3194,15 +3287,16 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi const firstId = result?.tasks?.[0]?.id || ""; const ids = Array.isArray(result?.tasks) ? result.tasks.map((task: any) => String(task?.id || "")).filter(Boolean) : []; const msg = `已创建 ${ids.length || submittingItems.length} 个任务${ids.length > 0 ? `:${ids.join(" / ")}` : ""}`; + mergeCreatedTasksIntoCurrentView(Array.isArray(result?.tasks) ? result.tasks : [], result?.queue || null); setNotice(msg); addNotification("success", msg); setPrompt(""); setReferenceTaskId(""); setBatchConfirmed(false); - selectedIdRef.current = firstId; - if (selectedQueueId !== submitQueueId) setTasksData(null); setQueueId(submitQueueId); - await load(firstId, true, submitQueueId); + if (firstId && taskVisibleInQueueFilter(result?.tasks?.[0], selectedQueueId) && taskMatchesCurrentSearch(result?.tasks?.[0])) { + void ensureTraceSummary(String(firstId), false).catch((err) => setError(errorText(err, "加载 Codex Trace Summary 失败"))); + } }, "Codex 任务入队失败"); enqueueInFlightRef.current = false; setSubmitting(false);