fix: preserve code queue prompt observation fields
This commit is contained in:
@@ -234,8 +234,10 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
fileItem("src/components/microservices/mdtodo/src/index.ts"),
|
||||
fileItem("src/components/microservices/decision-center/src/index.ts"),
|
||||
fileItem("src/components/microservices/code-queue-mgr/src/index.ts"),
|
||||
fileItem("src/components/microservices/code-queue-mgr/src/prompt-observation.ts"),
|
||||
fileItem("scripts/src/deploy.ts"),
|
||||
fileItem("scripts/src/e2e.ts"),
|
||||
fileItem("scripts/code-queue-prompt-observation-test.ts"),
|
||||
fileItem("scripts/src/artifact-registry.ts"),
|
||||
);
|
||||
} else {
|
||||
@@ -243,8 +245,10 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
}
|
||||
if (options.scriptsTypecheck) {
|
||||
items.push(commandItem("typescript:scripts", ["bunx", "tsc", "-p", "scripts/tsconfig.json", "--noEmit", "--pretty", "false"], 120_000));
|
||||
items.push(commandItem("code-queue:prompt-observation-contract", ["bun", "scripts/code-queue-prompt-observation-test.ts"], 30_000));
|
||||
} else {
|
||||
items.push(skippedItem("typescript:scripts", "scripts TypeScript typecheck is opt-in", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:prompt-observation-contract", "prompt observation contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
}
|
||||
if (options.logs) {
|
||||
items.push(unifiedLogRotationItem());
|
||||
|
||||
@@ -228,6 +228,8 @@ function compactSummary(summary: unknown, options: CodexTaskOptions, taskId: str
|
||||
const transcriptCount = asNumber(record.transcriptCount, 0);
|
||||
const transcriptMaxSeq = transcriptCount > 0 ? record.transcriptMaxSeq ?? null : null;
|
||||
const initialPrompt = asString(record.initialPrompt ?? record.prompt);
|
||||
const initialPromptView = textView(initialPrompt, options.full, 3000);
|
||||
const basePromptView = textView(asString(record.basePrompt), options.full, 2000);
|
||||
return {
|
||||
id: record.id ?? taskId,
|
||||
queueId: record.queueId ?? null,
|
||||
@@ -256,8 +258,9 @@ function compactSummary(summary: unknown, options: CodexTaskOptions, taskId: str
|
||||
startedAt: record.startedAt ?? null,
|
||||
updatedAt: record.updatedAt ?? null,
|
||||
finishedAt: record.finishedAt ?? null,
|
||||
initialPrompt: textView(initialPrompt, options.full, 3000),
|
||||
basePrompt: textView(asString(record.basePrompt), options.full, 2000),
|
||||
...(options.full
|
||||
? { initialPrompt: initialPromptView, basePrompt: basePromptView }
|
||||
: { initialPromptPreview: initialPromptView, basePromptPreview: basePromptView }),
|
||||
referenceTaskIds: record.referenceTaskIds ?? [],
|
||||
referenceInjection: record.referenceInjection ?? null,
|
||||
lastAssistantMessage: compactLastAssistant(record.lastAssistantMessage, options.full),
|
||||
@@ -825,7 +828,7 @@ function compactTaskMutationResponse(task: unknown, options: CompactTaskMutation
|
||||
startedAt: record.startedAt ?? null,
|
||||
updatedAt: record.updatedAt ?? null,
|
||||
finishedAt: record.finishedAt ?? null,
|
||||
prompt: textView(prompt, options.fullPrompt === true, 1200),
|
||||
...(options.fullPrompt === true ? { prompt: textView(prompt, true, 1200) } : { promptPreview: textView(prompt, false, 1200) }),
|
||||
commands: taskId.length === 0 ? null : {
|
||||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||||
|
||||
+169
-3
@@ -139,6 +139,7 @@ const FRONTEND_CHECK_NAMES = [
|
||||
"frontend:code-queue-integrated-visible",
|
||||
"frontend:code-queue-enqueue-await-smoke",
|
||||
"frontend:code-queue-summary-mobile-wrap",
|
||||
"frontend:code-queue-long-prompt-observation",
|
||||
"frontend:code-queue-initial-prompt-full-expand",
|
||||
"frontend:code-queue-trace-full-load",
|
||||
"frontend:code-queue-judge-wrap",
|
||||
@@ -486,8 +487,10 @@ function wantsPrefix(options: E2ERunOptions, prefix: string): boolean {
|
||||
}
|
||||
|
||||
function publicUrls(config: UniDeskConfig): PublicUrls {
|
||||
const frontendTarget = String(process.env.UNIDESK_E2E_FRONTEND || "prod").trim().toLowerCase();
|
||||
const frontendPort = frontendTarget === "dev" ? config.network.devFrontend.port : config.network.frontend.port;
|
||||
return {
|
||||
frontendUrl: `http://${config.network.publicHost}:${config.network.frontend.port}`,
|
||||
frontendUrl: `http://${config.network.publicHost}:${frontendPort}`,
|
||||
providerIngressHealthUrl: `http://${config.network.publicHost}:${config.network.providerIngress.port}/health`,
|
||||
providerIngressWsUrl: `ws://${config.network.publicHost}:${config.network.providerIngress.port}/ws/provider`,
|
||||
blockedCoreUrl: `http://${config.network.publicHost}:${config.network.core.port}`,
|
||||
@@ -750,6 +753,148 @@ async function runCodeQueueEnqueueAwaitSmoke(page: Page): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
async function runCodeQueueLongPromptObservation(page: Page): Promise<any> {
|
||||
const marker = `E2E_LONG_PROMPT_TAIL_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
|
||||
const submitQueueId = "e2e-long-prompt";
|
||||
const filler = Array.from({ length: 76 }, (_, index) => `long-prompt-line-${String(index + 1).padStart(2, "0")} ${"abcdefghijklmnopqrstuvwxyz0123456789".repeat(2)}`).join("\n");
|
||||
const prompt = [
|
||||
"Code Queue long prompt observation regression",
|
||||
"",
|
||||
filler,
|
||||
"",
|
||||
"验收标准:",
|
||||
`必须在所有原始 prompt 观察层保留这个唯一 tail marker: ${marker}`,
|
||||
].join("\n");
|
||||
const promptChars = prompt.length;
|
||||
if (promptChars <= 2500) throw new Error(`long prompt fixture too short: ${promptChars}`);
|
||||
const apiFetch = async (path: string, init: RequestInit = {}): Promise<any> => {
|
||||
const response = await fetch(path, {
|
||||
credentials: "same-origin",
|
||||
headers: { "content-type": "application/json", ...(init.headers || {}) },
|
||||
...init,
|
||||
});
|
||||
const text = await response.text();
|
||||
let body: any = null;
|
||||
try { body = text.length > 0 ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
return { ok: response.ok, status: response.status, body };
|
||||
};
|
||||
|
||||
await page.getByTestId("code-queue-filter-select").selectOption("__all__").catch(() => undefined);
|
||||
await page.getByTestId("code-queue-id-select").selectOption(submitQueueId).catch(async () => {
|
||||
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;
|
||||
return Array.from(select?.options || []).some((option) => option.value === queueId);
|
||||
}, submitQueueId, { timeout: 10000 });
|
||||
await page.getByTestId("code-queue-id-select").selectOption(submitQueueId);
|
||||
});
|
||||
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);
|
||||
await page.waitForFunction(() => {
|
||||
const button = document.querySelector('[data-testid="codex-enqueue-button"]') as HTMLButtonElement | null;
|
||||
return button !== null && !button.disabled;
|
||||
}, undefined, { timeout: 5000 });
|
||||
const responsePromise = page.waitForResponse((response) => isCodeQueueTaskEnqueueRequest(response.url(), response.request().method()), { timeout: 30000 });
|
||||
await page.getByTestId("codex-enqueue-button").click();
|
||||
const response = await responsePromise;
|
||||
const createBody = await response.json().catch((error: unknown) => ({ parseError: error instanceof Error ? error.message : String(error) }));
|
||||
const createdTask = Array.isArray(createBody?.tasks) ? createBody.tasks[0] : null;
|
||||
const taskId = String(createdTask?.id || "");
|
||||
if (!taskId) return { checked: true, marker, promptChars, createStatus: response.status(), taskId: "", error: "missing task id", createBody };
|
||||
await page.waitForSelector(`[data-testid="codex-task-${taskId}"]`, { timeout: 30000 }).catch(() => undefined);
|
||||
|
||||
const overview = await page.evaluate(async (id) => {
|
||||
const response = await fetch(`/api/microservices/code-queue/proxy/api/tasks/overview?limit=120&transcriptLimit=0&compact=1&selected=1&includeActive=1&stats=0&afterSeq=0&preferId=${encodeURIComponent(String(id))}`, { credentials: "same-origin" });
|
||||
const body = await response.json().catch(() => null);
|
||||
return { ok: response.ok, status: response.status, body };
|
||||
}, taskId);
|
||||
const meta = await page.evaluate(async (id) => {
|
||||
const response = await fetch(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(String(id))}?meta=1`, { credentials: "same-origin" });
|
||||
const body = await response.json().catch(() => null);
|
||||
return { ok: response.ok, status: response.status, body };
|
||||
}, taskId);
|
||||
const initialPrompt = await page.evaluate(async (id) => {
|
||||
const response = await fetch(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(String(id))}/prompt?part=initial`, { credentials: "same-origin" });
|
||||
const body = await response.json().catch(() => null);
|
||||
return { ok: response.ok, status: response.status, body };
|
||||
}, taskId);
|
||||
|
||||
await page.getByTestId(`codex-task-${taskId}`).click().catch(() => undefined);
|
||||
await page.waitForSelector('[data-testid="codex-initial-prompt-base"]', { timeout: 15000 }).catch(() => undefined);
|
||||
const ui = await page.evaluate(() => {
|
||||
const base = document.querySelector('[data-testid="codex-initial-prompt-base"]') as HTMLElement | null;
|
||||
return {
|
||||
baseText: base?.innerText || "",
|
||||
baseChars: (base?.innerText || "").length,
|
||||
};
|
||||
});
|
||||
|
||||
const overviewTasks = Array.isArray(overview.body?.tasks) ? overview.body.tasks : [];
|
||||
const overviewTask = overviewTasks.find((task: any) => String(task?.id || "") === taskId) || null;
|
||||
const selectedTask = overview.body?.selected?.task || null;
|
||||
const metaTask = meta.body?.task || null;
|
||||
const createPrompt = String(createdTask?.prompt || "");
|
||||
const createDisplayPrompt = String(createdTask?.displayPrompt || "");
|
||||
const createPreview = createdTask?.displayPromptPreview || createdTask?.promptPreview || null;
|
||||
const overviewPrompt = String(overviewTask?.prompt || "");
|
||||
const overviewDisplayPrompt = String(overviewTask?.displayPrompt || "");
|
||||
const overviewPreview = overviewTask?.displayPromptPreview || overviewTask?.promptPreview || null;
|
||||
const selectedDisplayPrompt = String(selectedTask?.displayPrompt || "");
|
||||
const selectedPreview = selectedTask?.displayPromptPreview || selectedTask?.promptPreview || null;
|
||||
const metaPrompt = String(metaTask?.prompt || "");
|
||||
const metaBasePrompt = String(metaTask?.basePrompt || "");
|
||||
const metaDisplayPrompt = String(metaTask?.displayPrompt || "");
|
||||
const promptText = String(initialPrompt.body?.text || "");
|
||||
const previewHasTruncationMarker = [createPreview?.text, overviewPreview?.text, selectedPreview?.text].some((value) => String(value || "").includes("...<truncated>"));
|
||||
const originalValues = [createPrompt, createDisplayPrompt, overviewPrompt, overviewDisplayPrompt, selectedDisplayPrompt, metaPrompt, metaBasePrompt, metaDisplayPrompt, promptText];
|
||||
await apiFetch(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/interrupt`, { method: "POST", body: "{}" }).catch(() => null);
|
||||
return {
|
||||
checked: true,
|
||||
marker,
|
||||
taskId,
|
||||
promptChars,
|
||||
createStatus: response.status(),
|
||||
createOk: createBody?.ok === true,
|
||||
createPromptChars: createPrompt.length,
|
||||
createDisplayPromptChars: createDisplayPrompt.length,
|
||||
overviewStatus: overview.status,
|
||||
overviewTaskFound: overviewTask !== null,
|
||||
overviewPromptChars: overviewPrompt.length,
|
||||
overviewDisplayPromptChars: overviewDisplayPrompt.length,
|
||||
selectedTaskFound: selectedTask !== null && String(selectedTask?.id || "") === taskId,
|
||||
selectedDisplayPromptChars: selectedDisplayPrompt.length,
|
||||
metaStatus: meta.status,
|
||||
metaPromptChars: metaPrompt.length,
|
||||
metaBasePromptChars: metaBasePrompt.length,
|
||||
metaDisplayPromptChars: metaDisplayPrompt.length,
|
||||
initialPromptStatus: initialPrompt.status,
|
||||
initialPromptChars: Number(initialPrompt.body?.chars || promptText.length),
|
||||
uiBaseChars: ui.baseChars,
|
||||
preview: {
|
||||
create: createPreview,
|
||||
overview: overviewPreview,
|
||||
selected: selectedPreview,
|
||||
previewHasTruncationMarker,
|
||||
createPromptPreviewFieldPresent: createdTask?.promptPreview !== undefined,
|
||||
overviewPromptPreviewFieldPresent: overviewTask?.promptPreview !== undefined,
|
||||
selectedPromptPreviewFieldPresent: selectedTask?.promptPreview !== undefined,
|
||||
},
|
||||
checks: {
|
||||
createOriginalHasTail: createPrompt.includes(marker) && createDisplayPrompt.includes(marker),
|
||||
overviewOriginalHasTail: overviewPrompt.includes(marker) && overviewDisplayPrompt.includes(marker),
|
||||
selectedOriginalHasTail: selectedDisplayPrompt.includes(marker),
|
||||
metaOriginalHasTail: metaPrompt.includes(marker) && metaBasePrompt.includes(marker) && metaDisplayPrompt.includes(marker),
|
||||
initialPromptHasTail: promptText.includes(marker),
|
||||
uiFullPromptHasTail: String(ui.baseText || "").includes(marker),
|
||||
originalsHaveNoTruncationMarker: originalValues.every((value) => !value.includes("...<truncated>")),
|
||||
previewFieldsExplicit: createdTask?.promptPreview !== undefined && overviewTask?.promptPreview !== undefined && selectedTask?.promptPreview !== undefined,
|
||||
previewCanTruncate: Boolean(createPreview?.truncated || overviewPreview?.truncated || selectedPreview?.truncated),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function runPsql(config: UniDeskConfig, sql: string): { ok: boolean; stdout: string; stderr: string; exitCode: number | null } {
|
||||
const result = runCommand([
|
||||
"docker",
|
||||
@@ -1486,8 +1631,10 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
|
||||
page.on("dialog", (dialog) => dialog.accept());
|
||||
await page.goto(urls.frontendUrl, { waitUntil: "domcontentloaded", timeout: 15000 });
|
||||
await page.waitForSelector('[data-testid="login-screen"]', { timeout: 10000 });
|
||||
await page.fill('input[name="username"]', config.auth.username);
|
||||
await page.fill('input[name="password"]', config.auth.password);
|
||||
const frontendAuthUsername = process.env.UNIDESK_E2E_AUTH_USERNAME || config.auth.username;
|
||||
const frontendAuthPassword = process.env.UNIDESK_E2E_AUTH_PASSWORD || config.auth.password;
|
||||
await page.fill('input[name="username"]', frontendAuthUsername);
|
||||
await page.fill('input[name="password"]', frontendAuthPassword);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForSelector('[data-testid="app-shell"]', { timeout: 10000 });
|
||||
await page.waitForFunction(() => document.querySelector('[data-testid="conn-text"]')?.textContent?.includes("核心在线"), undefined, { timeout: 15000 });
|
||||
@@ -1547,6 +1694,7 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
|
||||
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 codeQueueLongPromptMetrics: any = { checked: false };
|
||||
let codeQueuePromptDefaultEmpty = false;
|
||||
let codeQueueSubmitGuard: any = { batchRowVisible: false, disabledBeforeConfirm: false, enabledAfterConfirm: false, waitElementMissingBeforeSubmit: false };
|
||||
let codeQueueEnqueueAwaitSmoke: any = { checked: false };
|
||||
@@ -1995,6 +2143,9 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
|
||||
if (wants("frontend:code-queue-enqueue-await-smoke")) {
|
||||
codeQueueEnqueueAwaitSmoke = await runCodeQueueEnqueueAwaitSmoke(page);
|
||||
}
|
||||
if (wants("frontend:code-queue-long-prompt-observation")) {
|
||||
codeQueueLongPromptMetrics = await runCodeQueueLongPromptObservation(page);
|
||||
}
|
||||
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,
|
||||
@@ -3049,6 +3200,21 @@ async function frontendCheck(config: UniDeskConfig, urls: PublicUrls, checks: E2
|
||||
&& (codeQueueEnqueueAwaitSmoke.interrupt?.ok === true || codeQueueEnqueueAwaitSmoke.interrupt?.status === 409),
|
||||
{ codeQueueEnqueueAwaitSmoke });
|
||||
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-long-prompt-observation",
|
||||
codeQueueLongPromptMetrics.checked === true
|
||||
&& codeQueueLongPromptMetrics.createOk === true
|
||||
&& /^codex_\d+_[A-Za-z0-9_-]+$/u.test(String(codeQueueLongPromptMetrics.taskId || ""))
|
||||
&& Number(codeQueueLongPromptMetrics.promptChars || 0) > 2500
|
||||
&& codeQueueLongPromptMetrics.checks?.createOriginalHasTail === true
|
||||
&& codeQueueLongPromptMetrics.checks?.overviewOriginalHasTail === true
|
||||
&& codeQueueLongPromptMetrics.checks?.selectedOriginalHasTail === true
|
||||
&& codeQueueLongPromptMetrics.checks?.metaOriginalHasTail === true
|
||||
&& codeQueueLongPromptMetrics.checks?.initialPromptHasTail === true
|
||||
&& codeQueueLongPromptMetrics.checks?.uiFullPromptHasTail === true
|
||||
&& codeQueueLongPromptMetrics.checks?.originalsHaveNoTruncationMarker === true
|
||||
&& codeQueueLongPromptMetrics.checks?.previewFieldsExplicit === true
|
||||
&& codeQueueLongPromptMetrics.checks?.previewCanTruncate === true,
|
||||
{ codeQueueLongPromptMetrics });
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user