feat: harden code queue runtime UX

- add queue merge controls and no-cache overview loading\n- improve runtime probes, judge retries, and task trace rendering\n- extend CLI/E2E/docs coverage for code queue and user services
This commit is contained in:
Codex
2026-05-14 02:20:48 +00:00
parent 79154d9b3f
commit b36d7f94d7
26 changed files with 1323 additions and 349 deletions
File diff suppressed because one or more lines are too long
+17 -10
View File
@@ -351,7 +351,7 @@ h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.08em; }
align-items: start;
}
.overview-grid .panel:nth-child(n+3), .dispatch-grid .panel:first-child, .scheduled-task-page .panel:first-child, .scheduled-task-page .panel:nth-child(3), .topology-grid .panel:nth-child(3) { grid-column: 1 / -1; }
.overview-grid .panel:nth-child(n+3), .dispatch-grid .panel:first-child, .scheduled-task-page .panel:first-child, .scheduled-task-page .panel:nth-child(2), .scheduled-task-page .panel:nth-child(3), .topology-grid .panel:nth-child(3) { grid-column: 1 / -1; }
.panel {
min-width: 0;
@@ -381,8 +381,8 @@ h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.08em; }
}
.metric-card {
min-height: 76px;
padding: 10px;
min-height: 52px;
padding: 7px;
border: 1px solid var(--line-soft);
background: var(--panel-2);
}
@@ -405,9 +405,9 @@ h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.08em; }
letter-spacing: 0.12em;
}
.metric-value {
margin-top: 8px;
margin-top: 4px;
color: var(--text);
font-size: 23px;
font-size: 16px;
font-weight: 760;
}
.metric-hint { margin-top: 3px; color: var(--muted); font-size: 11px; }
@@ -1330,10 +1330,12 @@ td { color: var(--text); }
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 8px;
min-width: 0;
}
.label-card { padding: 8px; }
.label-card span { display: block; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; }
.label-card strong { display: block; margin: 5px 0; }
.label-card { padding: 8px; min-width: 0; }
.label-card span { display: block; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; overflow-wrap: anywhere; }
.label-card strong { display: block; margin: 5px 0; overflow-wrap: anywhere; }
.label-card code { overflow-wrap: anywhere; white-space: normal; }
.dispatch-form {
display: grid;
@@ -2349,7 +2351,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
grid-template-areas:
"queue-select queue-select"
"rename rename"
". create";
"merge create";
gap: 6px;
min-width: 0;
align-items: center;
@@ -2358,6 +2360,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
grid-area: queue-select;
}
.codex-rename-queue-btn,
.codex-merge-queue-btn,
.codex-create-queue-btn {
min-height: 32px;
white-space: nowrap;
@@ -2370,6 +2373,9 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
.codex-create-queue-btn {
grid-area: create;
}
.codex-merge-queue-btn {
grid-area: merge;
}
.codex-reference-field {
display: grid;
gap: 5px;
@@ -5365,9 +5371,10 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
grid-template-areas:
"queue-select"
"rename"
"merge"
"create";
}
.codex-rename-queue-btn, .codex-create-queue-btn { width: 100%; }
.codex-rename-queue-btn, .codex-merge-queue-btn, .codex-create-queue-btn { width: 100%; }
.codex-session-title-toggle { min-height: 40px; padding: 9px 15px; font-size: 14px; }
.codex-attempt-cycle-head { align-items: flex-start; }
.codex-attempt-cycle-head code {
+32 -32
View File
@@ -378,6 +378,38 @@ export function BaiduNetdiskPage({ microservices, onRaw, apiBaseUrl = "/api" }:
h(MetricCard, { label: "Quota", value: fmtBytes(quota.used), hint: quota.total ? `${quota.usedPercent || 0}% / ${fmtBytes(quota.total)}` : "授权后刷新" }),
h(MetricCard, { label: "Transfers", value: numberText(jobs.length), hint: `running ${state.transfers?.counts?.running || 0} / failed ${state.transfers?.counts?.failed || 0}` }),
),
h(Panel, { title: "文件浏览器", eyebrow: currentDir, className: "baidu-files-panel", loading: state.loading,
actions: h("div", { className: "panel-actions inline-actions" },
h("button", { type: "button", className: "ghost-btn", onClick: () => { const parent = pathParent(currentDir, appRoot); setCurrentDir(parent); void loadFiles(parent); }, disabled: !loggedIn || currentDir === appRoot }, "上级"),
h("button", { type: "button", className: "ghost-btn", onClick: () => loadFiles(currentDir), disabled: !loggedIn }, "刷新文件"),
h(RawButton, { title: "Baidu Files", data: state.files, onOpen: onRaw, testId: "raw-baidu-files" }),
),
},
h("form", { className: "baidu-pathbar", onSubmit: (event: any) => { event.preventDefault(); void loadFiles(currentDir); } },
h("input", { value: currentDir, onChange: (event: any) => setCurrentDir(event.target.value), disabled: !loggedIn }),
h("button", { type: "submit", className: "ghost-btn", disabled: !loggedIn }, "打开路径"),
),
h("form", { className: "baidu-pathbar", onSubmit: createFolder },
h("input", { value: folderName, onChange: (event: any) => setFolderName(event.target.value), placeholder: "新文件夹名称", disabled: !loggedIn }),
h("button", { type: "submit", className: "primary-btn", disabled: !loggedIn || !folderName.trim() }, "新建文件夹"),
),
!loggedIn ? h(EmptyState, { title: "等待授权", text: "登录后通过 /api/files 读取工作目录文件列表" }) : files.length === 0 ? h(EmptyState, { title: "目录为空", text: "可以从 staging 目录上传文件或新建文件夹" }) :
h("div", { className: "table-wrap", "data-testid": "baidu-netdisk-file-table" }, h("table", null,
h("thead", null, h("tr", null, h("th", null, "名称"), h("th", null, "类型"), h("th", null, "大小"), h("th", null, "修改时间"), h("th", null, "fs_id"), h("th", null, "操作"))),
h("tbody", null, files.map((file: any) => h("tr", { key: file.fsId || file.path },
h("td", null, h("strong", null, file.serverFilename || file.path), h("code", null, file.path || "--")),
h("td", null, h(StatusBadge, { status: file.isDir ? "queued" : "private" }, file.isDir ? "DIR" : "FILE")),
h("td", null, file.isDir ? "--" : fmtBytes(file.size)),
h("td", null, file.serverMtime ? fmtDate(file.serverMtime * 1000) : "--"),
h("td", null, h("code", null, file.fsId || "--")),
h("td", null, h("div", { className: "inline-actions" },
file.isDir ? h("button", { type: "button", className: "ghost-btn", onClick: () => { setCurrentDir(file.path); void loadFiles(file.path); } }, "打开") :
h("button", { type: "button", className: "ghost-btn", onClick: () => setDownloadForm((prev: any) => ({ ...prev, fsId: file.fsId })) }, "填入下载"),
h("button", { type: "button", className: "ghost-btn", onClick: () => deleteRemote(file.path), disabled: state.actionLoading }, "删除"),
)),
))),
)),
),
h("div", { className: "baidu-netdisk-grid" },
h(Panel, {
title: "配置与文档",
@@ -474,38 +506,6 @@ export function BaiduNetdiskPage({ microservices, onRaw, apiBaseUrl = "/api" }:
h("div", { className: "microservice-ref-card" }, h("span", null, "Quota"), h("strong", null, `${fmtBytes(quota.used)} / ${fmtBytes(quota.total)}`), h("code", null, `${quota.usedPercent || 0}% used`)),
) : h(EmptyState, { title: "尚未登录", text: "扫码授权后这里会显示账号、UID、会员状态和容量" }),
),
h(Panel, { title: "文件浏览器", eyebrow: currentDir, className: "baidu-files-panel", loading: state.loading,
actions: h("div", { className: "panel-actions inline-actions" },
h("button", { type: "button", className: "ghost-btn", onClick: () => { const parent = pathParent(currentDir, appRoot); setCurrentDir(parent); void loadFiles(parent); }, disabled: !loggedIn || currentDir === appRoot }, "上级"),
h("button", { type: "button", className: "ghost-btn", onClick: () => loadFiles(currentDir), disabled: !loggedIn }, "刷新文件"),
h(RawButton, { title: "Baidu Files", data: state.files, onOpen: onRaw, testId: "raw-baidu-files" }),
),
},
h("form", { className: "baidu-pathbar", onSubmit: (event: any) => { event.preventDefault(); void loadFiles(currentDir); } },
h("input", { value: currentDir, onChange: (event: any) => setCurrentDir(event.target.value), disabled: !loggedIn }),
h("button", { type: "submit", className: "ghost-btn", disabled: !loggedIn }, "打开路径"),
),
h("form", { className: "baidu-pathbar", onSubmit: createFolder },
h("input", { value: folderName, onChange: (event: any) => setFolderName(event.target.value), placeholder: "新文件夹名称", disabled: !loggedIn }),
h("button", { type: "submit", className: "primary-btn", disabled: !loggedIn || !folderName.trim() }, "新建文件夹"),
),
!loggedIn ? h(EmptyState, { title: "等待授权", text: "登录后通过 /api/files 读取工作目录文件列表" }) : files.length === 0 ? h(EmptyState, { title: "目录为空", text: "可以从 staging 目录上传文件或新建文件夹" }) :
h("div", { className: "table-wrap", "data-testid": "baidu-netdisk-file-table" }, h("table", null,
h("thead", null, h("tr", null, h("th", null, "名称"), h("th", null, "类型"), h("th", null, "大小"), h("th", null, "修改时间"), h("th", null, "fs_id"), h("th", null, "操作"))),
h("tbody", null, files.map((file: any) => h("tr", { key: file.fsId || file.path },
h("td", null, h("strong", null, file.serverFilename || file.path), h("code", null, file.path || "--")),
h("td", null, h(StatusBadge, { status: file.isDir ? "queued" : "private" }, file.isDir ? "DIR" : "FILE")),
h("td", null, file.isDir ? "--" : fmtBytes(file.size)),
h("td", null, file.serverMtime ? fmtDate(file.serverMtime * 1000) : "--"),
h("td", null, h("code", null, file.fsId || "--")),
h("td", null, h("div", { className: "inline-actions" },
file.isDir ? h("button", { type: "button", className: "ghost-btn", onClick: () => { setCurrentDir(file.path); void loadFiles(file.path); } }, "打开") :
h("button", { type: "button", className: "ghost-btn", onClick: () => setDownloadForm((prev: any) => ({ ...prev, fsId: file.fsId })) }, "填入下载"),
h("button", { type: "button", className: "ghost-btn", onClick: () => deleteRemote(file.path), disabled: state.actionLoading }, "删除"),
)),
))),
)),
),
h(Panel, { title: "传输任务", eyebrow: "staging path jobs", className: "baidu-transfers-panel", loading: state.actionLoading,
actions: h("div", { className: "panel-actions inline-actions" },
h("button", { type: "button", className: "primary-btn", onClick: runSelfTest, disabled: !loggedIn || state.actionLoading, "data-testid": "baidu-netdisk-self-test" }, "运行自测"),
+245 -136
View File
@@ -150,6 +150,10 @@ function codexApi(apiBaseUrl: string, path: string): string {
return `${apiBaseUrl}/code-queue-direct${path}`;
}
function codexNoCacheOptions(): AnyRecord {
return { headers: { "cache-control": "no-cache", "x-unidesk-no-cache": "1" } };
}
function taskRows(data: any): any[] {
return Array.isArray(data?.tasks) ? data.tasks : [];
}
@@ -163,6 +167,19 @@ function taskSortValue(task: any): number {
return Number.isFinite(time) ? time : 0;
}
function taskQueueOrderValue(task: any): number {
const time = timestampMs(task?.queueEnteredAt) ?? timestampMs(task?.createdAt) ?? timestampMs(task?.updatedAt);
return time ?? 0;
}
function compareTaskQueueOrder(left: any, right: any): number {
const queueDelta = taskQueueOrderValue(left) - taskQueueOrderValue(right);
if (queueDelta !== 0) return queueDelta;
const createdDelta = (timestampMs(left?.createdAt) ?? 0) - (timestampMs(right?.createdAt) ?? 0);
if (createdDelta !== 0) return createdDelta;
return String(left?.id || "").localeCompare(String(right?.id || ""));
}
function mergeTaskRows(groups: any[][], activeTaskId = ""): any[] {
const byId = new Map<string, any>();
for (const group of groups) {
@@ -292,39 +309,25 @@ function queueRunnableTaskId(queueRows: any[], queueId: string, rows: any[]): st
return String(rows.find((task: any) => String(task?.status || "") === "queued" || String(task?.status || "") === "retry_wait")?.id || "");
}
async function loadTaskQueue(apiBaseUrl: string, healthResult: any, queueId = allQueuesId, searchQuery = ""): Promise<any> {
const suffix = taskListQuerySuffix(queueId, searchQuery);
try {
return await requestJson(codexApi(apiBaseUrl, `/api/tasks?limit=${codexInitialTaskLimit}&lite=1&devReady=0${suffix}`));
} catch {
const statuses = ["running", "judging", "retry_wait", "queued"];
const results = await Promise.all(statuses.map(async (status) => {
try {
return await requestJson(codexApi(apiBaseUrl, `/api/tasks?status=${encodeURIComponent(status)}&limit=80&lite=1&devReady=0${suffix}`));
} catch {
return null;
}
}));
const historyResult = await requestJson(codexApi(apiBaseUrl, `/api/tasks?limit=${codexInitialTaskLimit}&lite=1&devReady=0${suffix}`)).catch(() => null);
const queue = results.find((item) => item?.queue)?.queue || historyResult?.queue || healthResult?.queue || healthResult?.body?.queue || {};
const rows = mergeTaskRows([...results.map((item) => taskRows(item)), taskRows(historyResult)], String(queue?.activeTaskId || ""));
if (rows.length > 0) return { ok: true, queue, statistics: historyResult?.statistics || results.find((item) => item?.statistics)?.statistics || null, tasks: rows };
return requestJson(codexApi(apiBaseUrl, `/api/tasks?limit=5&lite=1&devReady=0${suffix}`));
}
async function loadTaskList(apiBaseUrl: string, queueId = allQueuesId, searchQuery = ""): Promise<any> {
return requestJson(codexApi(
apiBaseUrl,
`/api/tasks/overview?limit=${codexInitialTaskLimit}&transcriptLimit=1&compact=1&selected=0${taskListQuerySuffix(queueId, searchQuery)}`,
), codexNoCacheOptions());
}
async function loadTaskOverview(apiBaseUrl: string, preferId: string, afterSeq = 0, queueId = allQueuesId, searchQuery = ""): Promise<any> {
return requestJson(codexApi(
apiBaseUrl,
`/api/tasks/overview?limit=${codexInitialTaskLimit}&transcriptLimit=3&compact=1&afterSeq=${encodeURIComponent(String(Math.max(0, afterSeq)))}&preferId=${encodeURIComponent(preferId)}${taskListQuerySuffix(queueId, searchQuery)}`,
));
), codexNoCacheOptions());
}
async function loadTaskPage(apiBaseUrl: string, queueId: string, beforeId: string, limit = codexMoreTaskLimit, searchQuery = ""): Promise<any> {
return requestJson(codexApi(
apiBaseUrl,
`/api/tasks/overview?limit=${encodeURIComponent(String(limit))}&transcriptLimit=1&compact=1&selected=0&includeActive=0&stats=0&beforeId=${encodeURIComponent(beforeId)}${taskListQuerySuffix(queueId, searchQuery)}`,
));
), codexNoCacheOptions());
}
async function loadTaskTraceSummary(apiBaseUrl: string, taskId: string): Promise<any> {
@@ -564,7 +567,14 @@ function taskExecutionSummary(task: any): AnyRecord {
return execution && typeof execution === "object" && !Array.isArray(execution) ? execution : {};
}
function rawTaskStepCount(task: any): number {
function nonNegativeCount(value: any): number {
const numeric = Number(value);
return Number.isFinite(numeric) && numeric >= 0 ? Math.floor(numeric) : 0;
}
function canonicalTaskStepCount(task: any): number {
const summary = taskTraceSummary(task);
if (summary !== null) return traceSummaryStepCount(summary);
const value = Number(task?.stepCount ?? task?.llmStepCount ?? 0);
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : 0;
}
@@ -572,7 +582,7 @@ function rawTaskStepCount(task: any): number {
function traceSummaryStepCount(summary: AnyRecord | null): number {
const execution = objectRecord(summary?.execution) || {};
const value = Number(summary?.stepCount ?? summary?.llmStepCount ?? execution.stepCount ?? execution.llmStepCount ?? execution.toolCallCount ?? 0);
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : 0;
return nonNegativeCount(value);
}
function traceSummaryIsCurrent(task: any): boolean {
@@ -589,8 +599,7 @@ function traceSummaryIsCurrent(task: any): boolean {
return false;
}
}
const rawSteps = rawTaskStepCount(task);
return rawSteps <= 0 || traceSummaryStepCount(summary) >= rawSteps;
return true;
}
function taskBasePromptText(task: any): string {
@@ -618,16 +627,6 @@ function objectRecord(value: any): AnyRecord | null {
function taskProgressiveAttempts(task: any): any[] {
const summaryAttempts = taskTraceSummary(task)?.attempts;
if (Array.isArray(summaryAttempts) && summaryAttempts.length > 0) return summaryAttempts;
const attempts = taskAttempts(task);
if (attempts.length > 0) {
return attempts.map((attempt: any, index: number) => ({
...attempt,
index: Number(attempt?.index || index + 1),
execution: index === attempts.length - 1 ? taskExecutionSummary(task) : objectRecord(attempt?.execution) || {},
finalResponse: String(attempt?.finalResponse || attempt?.finalResponsePreview || (index === attempts.length - 1 ? taskFinalResponseText(task) : "")),
judge: objectRecord(attempt?.judge) || (index === attempts.length - 1 ? taskLastJudge(task) : null),
}));
}
const execution = taskExecutionSummary(task);
const finalResponse = taskFinalResponseText(task);
const judge = taskLastJudge(task);
@@ -743,6 +742,25 @@ function taskTraceSteps(task: any, attemptIndex: any = null): any[] {
return Array.isArray(task?._traceSteps) ? task._traceSteps : [];
}
function traceStepSeqValue(step: any): number {
const candidates = [step?.seq, ...(Array.isArray(step?.rawSeqs) ? step.rawSeqs : [])].map((value) => Number(value));
const finite = candidates.filter((value) => Number.isFinite(value));
return finite.length > 0 ? Math.max(...finite) : 0;
}
function traceStepsMaxSeq(steps: any[]): number {
return (Array.isArray(steps) ? steps : []).reduce((max, step) => Math.max(max, traceStepSeqValue(step)), 0);
}
function mergeTraceStepRows(existing: any[], incoming: any[]): any[] {
const byKey = new Map<string, any>();
for (const step of [...(Array.isArray(existing) ? existing : []), ...(Array.isArray(incoming) ? incoming : [])]) {
const key = String(step?.seq ?? `${step?.title || "step"}:${step?.at || ""}`);
byKey.set(key, { ...(byKey.get(key) || {}), ...step });
}
return Array.from(byKey.values()).sort((left, right) => traceStepSeqValue(left) - traceStepSeqValue(right));
}
function traceStepSummaryLines(step: any): string[] {
return (Array.isArray(step?.summaryLines) ? step.summaryLines : []).map((line: any) => String(line || ""));
}
@@ -834,39 +852,9 @@ function coalesceFileChangeTraceSteps(steps: any[]): any[] {
return merged;
}
function toolStepCountFromExecution(execution: AnyRecord): number {
const value = Number(execution?.toolCallCount);
if (Number.isFinite(value) && value >= 0) return Math.floor(value);
const total = Number(execution?.readCount || 0) + Number(execution?.editCount || 0) + Number(execution?.runCount || 0);
return Number.isFinite(total) && total >= 0 ? Math.floor(total) : 0;
}
function toolStepCountsFromTraceSteps(steps: any[]): AnyRecord {
const rows = Array.isArray(steps) ? steps : [];
const counts = rows.reduce((memo: AnyRecord, step: any) => {
const kind = String(step?.kind || "");
if (kind === "explored") memo.readCount += 1;
else if (kind === "edited") memo.editCount += 1;
else if (kind === "ran") memo.runCount += 1;
return memo;
}, { readCount: 0, editCount: 0, runCount: 0 });
counts.toolCallCount = counts.readCount + counts.editCount + counts.runCount;
return counts;
}
function executionSummaryWithDisplayedSteps(execution: AnyRecord, steps: any[]): AnyRecord {
if (steps.length === 0) {
const stepCount = toolStepCountFromExecution(execution);
return { ...execution, stepCount, llmStepCount: stepCount };
}
const counts = toolStepCountsFromTraceSteps(steps);
return {
...execution,
...counts,
stepCount: counts.toolCallCount,
llmStepCount: counts.toolCallCount,
traceLineCount: steps.length,
};
function canonicalExecutionSummary(execution: AnyRecord): AnyRecord {
const stepCount = nonNegativeCount(execution?.stepCount ?? execution?.llmStepCount ?? execution?.toolCallCount);
return { ...execution, stepCount, llmStepCount: stepCount };
}
function taskTraceStepsLoaded(task: any, attemptIndex: any = null): boolean {
@@ -1120,7 +1108,7 @@ function mergeTaskPatch(existingTask: any, patch: AnyRecord): AnyRecord {
merged._transcriptPreview = Object.prototype.hasOwnProperty.call(patch, "_transcriptPreview")
? patch._transcriptPreview
: existingTask?._transcriptPreview;
for (const key of ["_traceSummary", "_traceSummaryLoaded", "_traceSteps", "_traceStepsLoaded", "_traceStepsByAttempt", "_traceStepsLoadedByAttempt", "_traceStepDetails", "_promptDetails"]) {
for (const key of ["_traceSummary", "_traceSummaryLoaded", "_traceSteps", "_traceStepsLoaded", "_traceStepsByAttempt", "_traceStepsLoadedByAttempt", "_traceStepsNextAfterSeqByAttempt", "_traceStepDetails", "_promptDetails"]) {
if (!Object.prototype.hasOwnProperty.call(patch, key) && Object.prototype.hasOwnProperty.call(existingTask || {}, key)) {
merged[key] = existingTask[key];
}
@@ -1222,15 +1210,7 @@ function providerDefaultWorkdir(queue: any, providerId: string): string {
}
function taskStepCount(task: any): number {
const attempts = taskProgressiveAttempts(task).filter((attempt: any) => !isSyntheticAttemptSegment(attempt, attempt?.index));
if (attempts.length > 0) {
const attemptTotal = attempts.reduce((sum: number, attempt: any) => sum + toolStepCountFromExecution(attemptExecutionSummary(task, attempt)), 0);
if (attemptTotal > 0) return attemptTotal;
}
const executionTotal = toolStepCountFromExecution(taskExecutionSummary(task));
if (executionTotal > 0) return executionTotal;
const apiTotal = Number(task?.stepCount ?? task?.llmStepCount ?? 0);
return Number.isFinite(apiTotal) && apiTotal >= 0 ? Math.floor(apiTotal) : 0;
return canonicalTaskStepCount(task);
}
function TaskCard({ task, selected, onSelect, onCopy, onReference, onMarkRead, copied, markingRead }: AnyRecord) {
@@ -1556,14 +1536,13 @@ function ProgressivePromptBlock({ task, loading, onLoadPromptPart, testId = "cod
function ProgressiveExecutionSummary({ task, attempt, attemptIndex, loading, onLoadSteps, onLoadStep, testId = "codex-execution-summary" }: AnyRecord) {
const steps = coalesceFileChangeTraceSteps(taskTraceSteps(task, attemptIndex));
const execution = executionSummaryWithDisplayedSteps(attemptExecutionSummary(task, attempt), steps);
const execution = canonicalExecutionSummary(attemptExecutionSummary(task, attempt));
const summary = taskTraceSummary(task);
const stepDetails = taskTraceStepDetails(task);
const stepsLoaded = taskTraceStepsLoaded(task, attemptIndex);
const errorCount = Number(attempt?.errorCount ?? summary?.errorCount ?? traceStepErrorCount(steps));
const toolCount = Number(execution.toolCallCount || 0);
const summaryStepCountValue = Number(execution.stepCount ?? execution.llmStepCount ?? 0);
const summaryStepCount = Number.isFinite(summaryStepCountValue) && summaryStepCountValue >= 0 ? Math.floor(summaryStepCountValue) : 0;
const toolCount = nonNegativeCount(execution.toolCallCount);
const stepCount = nonNegativeCount(execution.stepCount ?? execution.llmStepCount ?? execution.toolCallCount);
const editedFiles = Array.isArray(execution.editedFiles) ? execution.editedFiles : [];
const commands = Array.isArray(execution.commands) ? execution.commands : [];
const synthetic = isSyntheticAttemptSegment(attempt, attemptIndex);
@@ -1571,11 +1550,6 @@ function ProgressiveExecutionSummary({ task, attempt, attemptIndex, loading, onL
const updatedAt = attemptExecutionUpdatedAt(task, attempt, attemptIndex, execution);
const recentUpdateLabel = `最近更新: ${fmtRelativeAge(updatedAt)}`;
const running = attemptExecutionIsRunning(task, attempt, attemptIndex);
const rawSteps = rawTaskStepCount(task);
const realAttemptCount = taskProgressiveAttempts(task).filter((item: any) => !isSyntheticAttemptSegment(item, item?.index)).length;
const canUseTaskStepFloor = !synthetic && steps.length === 0 && rawSteps > 0 && realAttemptCount <= 1;
const stepCount = canUseTaskStepFloor ? Math.max(summaryStepCount, rawSteps) : summaryStepCount;
const displayedToolCount = canUseTaskStepFloor ? Math.max(toolCount, stepCount) : toolCount;
return h("details", {
className: `codex-progressive-card codex-execution-summary ${running ? "running" : ""}`,
"data-testid": testId,
@@ -1591,13 +1565,13 @@ function ProgressiveExecutionSummary({ task, attempt, attemptIndex, loading, onL
h("strong", null, `执行过程摘要${labelSuffix}`),
running ? h("span", { className: "codex-summary-running-pill", "data-testid": `${testId}-running` }, "执行中") : null,
h("code", { title: updatedAt ? `最近更新: ${fmtDate(updatedAt)}` : recentUpdateLabel },
`${fmtDuration(execution.durationMs ?? execution.totalElapsedMs)} / ${displayedToolCount} tools / ${recentUpdateLabel}`),
`${fmtDuration(execution.durationMs ?? execution.totalElapsedMs)} / ${toolCount} tools / ${recentUpdateLabel}`),
),
h("div", { className: "codex-execution-digest" },
h("span", null, `read ${Number(execution.readCount || 0)}`),
h("span", null, `edit ${Number(execution.editCount || 0)}`),
h("span", null, `run ${Number(execution.runCount || 0)}`),
h("span", null, `STEP ${Number.isFinite(stepCount) ? Math.max(0, Math.floor(stepCount)) : 0}`),
h("span", null, `STEP ${stepCount}`),
errorCount > 0 ? h("span", { className: "codex-execution-error-pill", "data-testid": `${testId}-error-count` }, `Error ${errorCount}`) : null,
),
),
@@ -1853,6 +1827,9 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const traceStepsInFlightRef = useRef<Map<string, Promise<void>>>(new Map());
const traceStepInFlightRef = useRef<Map<string, Promise<void>>>(new Map());
const autoTraceLoadKeysRef = useRef<Set<string>>(new Set());
const eventRefreshTimerRef = useRef<number | null>(null);
const traceEventRefreshTimerRef = useRef<number | null>(null);
const traceEventRefreshPendingStepsRef = useRef(false);
const trackedLoadInFlightRef = useRef(false);
const initialLoadSkippedRef = useRef(Boolean(initialTasksData));
const locallyReadTaskIdsRef = useRef<Map<string, string>>(new Map());
@@ -1931,7 +1908,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const searchPagination = taskPagination(searchTasksData);
const sidebarTasks = searchActive ? searchTasks : tasks;
const unreadTerminalTasks = sidebarTasks.filter(taskIsUnreadTerminal);
const queuedTasks = sidebarTasks.filter((task: any) => !taskIsTerminal(task));
const queuedTasks = sidebarTasks.filter((task: any) => !taskIsTerminal(task)).sort(compareTaskQueueOrder);
const historyTasks = sidebarTasks.filter((task: any) => taskIsTerminal(task) && !taskIsUnreadTerminal(task));
const sidebarPagination = searchActive ? searchPagination : pagination;
const sidebarTotalTaskCount = searchActive ? Number(searchPagination.total ?? searchTasks.length) : totalTaskCount;
@@ -2071,7 +2048,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const timer = window.setTimeout(() => {
void (async () => {
try {
const result = await loadTaskQueue(apiBaseUrl, {}, selectedQueueId, query);
const result = await loadTaskList(apiBaseUrl, selectedQueueId, query);
if (token !== searchLoadTokenRef.current) return;
setSearchTasksData(applyLocalReadStateToResult(result));
} catch (err) {
@@ -2146,28 +2123,21 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const result = await loadTaskTraceSummary(apiBaseUrl, taskId);
if (token !== detailLoadTokenRef.current || selectedIdRef.current !== taskId) return;
const summary = result?.summary || {};
const currentTask = sessionCacheRef.current.get(taskId)?.task || {};
const summaryUpdatedAt = String(summary.updatedAt || "");
const currentUpdatedAt = String(currentTask?.updatedAt || "");
const summaryBehind = timestampIsAfter(currentUpdatedAt, summaryUpdatedAt) || rawTaskStepCount(currentTask) > traceSummaryStepCount(summary);
if (summaryBehind) inFlight.refreshAfter = true;
const effectiveUpdatedAt = summaryBehind
? latestTimestampValue(currentUpdatedAt, summaryUpdatedAt)
: summaryUpdatedAt || currentUpdatedAt;
publishCachedTask(taskId, {
id: taskId,
status: summaryBehind ? (currentTask?.status || summary.status) : summary.status,
updatedAt: effectiveUpdatedAt,
startedAt: summary.startedAt || currentTask?.startedAt,
finishedAt: summaryBehind ? (currentTask?.finishedAt || summary.finishedAt) : summary.finishedAt,
currentAttempt: summary.currentAttempt ?? currentTask?.currentAttempt,
maxAttempts: summary.maxAttempts ?? currentTask?.maxAttempts,
finalResponse: summaryBehind ? preferLongerText(currentTask, summary, "finalResponse") : summary.finalResponse,
lastJudge: summaryBehind ? (currentTask?.lastJudge || summary.lastJudge) : summary.lastJudge,
lastError: summaryBehind ? (currentTask?.lastError || summary.lastError) : summary.lastError,
attempts: summaryBehind
? preferRicherArray(currentTask, { attempts: Array.isArray(summary.attempts) ? summary.attempts : [] }, "attempts")
: Array.isArray(summary.attempts) ? summary.attempts : [],
status: summary.status,
updatedAt: summaryUpdatedAt,
startedAt: summary.startedAt,
finishedAt: summary.finishedAt,
currentAttempt: summary.currentAttempt,
maxAttempts: summary.maxAttempts,
finalResponse: summary.finalResponse,
lastJudge: summary.lastJudge,
lastError: summary.lastError,
attempts: Array.isArray(summary.attempts) ? summary.attempts : [],
stepCount: summary.stepCount,
llmStepCount: summary.llmStepCount,
timing: summary.timing,
_traceSummary: summary,
_traceSummaryLoaded: true,
@@ -2232,29 +2202,37 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
await promise;
}
async function ensureTraceSteps(attemptIndex: any = null): Promise<void> {
async function ensureTraceSteps(attemptIndex: any = null, options: AnyRecord = {}): Promise<void> {
const taskId = selectedIdRef.current;
if (!service || !taskId) return;
const cachedTask = sessionCacheRef.current.get(taskId)?.task;
const attemptKey = attemptIndex === null || attemptIndex === undefined || String(attemptIndex).length === 0 ? "" : String(attemptIndex);
if (taskTraceStepsLoaded(cachedTask, attemptKey || null)) return;
const key = `${taskId}:${attemptKey || "all"}`;
const loaded = taskTraceStepsLoaded(cachedTask, attemptKey || null);
const force = Boolean(options.force);
const incremental = Boolean(options.incremental);
if (loaded && !force) return;
const existingSteps = taskTraceSteps(cachedTask, attemptKey || null);
const afterSeq = incremental && existingSteps.length > 0 ? traceStepsMaxSeq(existingSteps) : 0;
const key = `${taskId}:${attemptKey || "all"}:${afterSeq}`;
const existing = traceStepsInFlightRef.current.get(key);
if (existing) return existing;
const token = detailLoadTokenRef.current;
if (selectedIdRef.current === taskId) setSelectedDetailLoading(true);
const promise = (async () => {
try {
const result = await loadTaskTraceSteps(apiBaseUrl, taskId, 0, 500, attemptKey || null);
const result = await loadTaskTraceSteps(apiBaseUrl, taskId, afterSeq, 500, attemptKey || null);
if (token !== detailLoadTokenRef.current || selectedIdRef.current !== taskId) return;
const steps = Array.isArray(result?.steps) ? result.steps : [];
const incomingSteps = Array.isArray(result?.steps) ? result.steps : [];
const steps = afterSeq > 0 ? mergeTraceStepRows(existingSteps, incomingSteps) : incomingSteps;
if (attemptKey) {
const currentTask = sessionCacheRef.current.get(taskId)?.task;
const byAttempt = objectRecord(currentTask?._traceStepsByAttempt) || {};
const loadedByAttempt = objectRecord(currentTask?._traceStepsLoadedByAttempt) || {};
const nextSeqByAttempt = objectRecord(currentTask?._traceStepsNextAfterSeqByAttempt) || {};
publishCachedTask(taskId, {
_traceStepsByAttempt: { ...byAttempt, [attemptKey]: steps },
_traceStepsLoadedByAttempt: { ...loadedByAttempt, [attemptKey]: true },
_traceStepsNextAfterSeqByAttempt: { ...nextSeqByAttempt, [attemptKey]: result?.nextAfterSeq },
}, token);
} else {
publishCachedTask(taskId, {
@@ -2303,6 +2281,19 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
await promise;
}
function refreshLoadedTraceSteps(taskId: string): void {
if (selectedIdRef.current !== taskId) return;
const cachedTask = sessionCacheRef.current.get(taskId)?.task;
if (!cachedTask) return;
if (taskTraceStepsLoaded(cachedTask, null)) {
void ensureTraceSteps(null, { force: true, incremental: true }).catch((err) => setError(errorText(err, "增量刷新 Trace Steps 失败")));
}
const loadedByAttempt = objectRecord(cachedTask?._traceStepsLoadedByAttempt) || {};
for (const attemptKey of Object.keys(loadedByAttempt).filter((key) => loadedByAttempt[key])) {
void ensureTraceSteps(attemptKey, { force: true, incremental: true }).catch((err) => setError(errorText(err, "增量刷新 Attempt Trace Steps 失败")));
}
}
async function loadTaskDetail(taskId: string, loadStartedAt?: number, queueMs?: number): Promise<void> {
if (!service || !taskId) return;
const detailStartedAt = performance.now();
@@ -2405,19 +2396,14 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const requestedCached = requestedId ? sessionCacheRef.current.get(requestedId) : null;
const requestedTranscript = Array.isArray(requestedCached?.task?.transcript) ? requestedCached.task.transcript : [];
const overviewAfterSeq = transcriptResumeSeq(requestedTranscript);
const healthResult = health || {};
let tasksResult = null;
try {
tasksResult = await loadTaskOverview(apiBaseUrl, requestedId, overviewAfterSeq, queueFilterId);
} catch {
tasksResult = await loadTaskQueue(apiBaseUrl, healthResult, queueFilterId);
}
tasksResult = await loadTaskOverview(apiBaseUrl, requestedId, overviewAfterSeq, queueFilterId);
if (token !== queueLoadTokenRef.current) {
if (trackLoad) trackedLoadInFlightRef.current = false;
return;
}
const queueMs = performance.now() - startedAt;
setHealth(healthResult);
setHealth(health || {});
const resultQueue = tasksResult?.queue || {};
const activeSortId = String(resultQueue?.activeTaskId || activeTaskIds(resultQueue)[0] || "");
let mergedTasksResult = tasksResult;
@@ -2447,9 +2433,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const overviewSelected = mergedTasksResult?.selected || null;
const overviewTask = overviewSelected?.task || null;
const overviewTranscript = Array.isArray(overviewSelected?.transcript) ? overviewSelected.transcript : null;
const nextId = latestRequestedId && (rows.some((task: any) => task.id === latestRequestedId) || String(overviewTask?.id || "") === latestRequestedId)
? latestRequestedId
: (queuePrimaryId || queueRunnableId || rows[0]?.id || "");
const nextId = latestRequestedId || queuePrimaryId || queueRunnableId || rows[0]?.id || "";
const previousId = selectedIdRef.current;
if (previousId !== nextId) detailLoadTokenRef.current += 1;
selectedIdRef.current = nextId;
@@ -2774,6 +2758,41 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
}, "修改 Codex queue 名称失败");
}
async function mergeQueue(): Promise<void> {
const targetQueueId = String(queueId || "default").trim() || "default";
const candidates = queueRows.filter((row: any) => String(row?.id || "") !== targetQueueId);
if (candidates.length === 0) {
const msg = "没有可合并的其他 queue;请先创建或选择另一个 queue。";
setNotice(msg);
return;
}
const proposed = typeof window === "undefined"
? ""
: window.prompt(`将源 queue 合并到当前 queue=${targetQueueId};源 queue 记录会保留且不会删除。请输入源 queue ID:`, String(candidates[0]?.id || ""));
const sourceQueueId = String(proposed || "").trim();
if (!sourceQueueId) return;
if (sourceQueueId === targetQueueId) {
setError("源 queue 和目标 queue 不能相同。");
return;
}
const confirmed = typeof window === "undefined" ? true : window.confirm(`确认把 queue=${sourceQueueId} 的全部任务合并到 queue=${targetQueueId}?源 queue 不会删除;任务会按原 queueEnteredAt/createdAt 时间顺序进入目标 queue。`);
if (!confirmed) return;
await guarded(async () => {
const result = await requestJson(codexApi(apiBaseUrl, `/api/queues/${encodeURIComponent(targetQueueId)}/merge`), { method: "POST", body: { sourceQueueId } });
if (result?.summary) {
setTasksData((previous: any) => previous ? { ...previous, queue: result.summary } : previous);
}
setQueueId(targetQueueId);
setSelectedQueueId(targetQueueId);
setTasksData(null);
const count = Number(result?.mergedTaskCount || 0);
const msg = `已将 queue=${sourceQueueId} 合并到 ${targetQueueId},移动 ${count} 个任务;源 queue 已保留。`;
setNotice(msg);
addNotification("success", msg);
await load(selectedIdRef.current, true, targetQueueId);
}, "合并 Codex queue 失败");
}
async function enqueue(event: any): Promise<void> {
event.preventDefault();
if (enqueueInFlightRef.current) {
@@ -2853,6 +2872,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
_traceStepsLoaded: false,
_traceStepsByAttempt: {},
_traceStepsLoadedByAttempt: {},
_traceStepsNextAfterSeqByAttempt: {},
_traceStepDetails: {},
};
sessionCacheRef.current.set(taskId, { ...(sessionCacheRef.current.get(taskId) || {}), task: updatedTask, complete: false, completeUpdatedAt: "" });
@@ -2956,6 +2976,82 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
if (isMobileQueueViewport()) setQueueSidebarOpen(false);
}
function patchRowsForTaskEvent(data: any, taskId: string, patch: AnyRecord): any {
if (!data || !Array.isArray(data?.tasks) || taskId.length === 0 || Object.keys(patch).length === 0) return data;
let changed = false;
const rows = taskRows(data).map((task: any) => {
if (String(task?.id || "") !== taskId) return task;
changed = true;
return taskWithLocalReadState(mergeTaskPatch(task, patch));
});
return changed ? { ...data, tasks: rows } : data;
}
function patchSidebarTaskFromEvent(taskId: string, patch: AnyRecord): void {
setTasksData((previous: any) => patchRowsForTaskEvent(previous, taskId, patch));
setSearchTasksData((previous: any) => patchRowsForTaskEvent(previous, taskId, patch));
}
function taskEventNeedsOverviewRefresh(event: AnyRecord, taskId: string, previousTask: any): boolean {
const type = String(event?.type || "");
if (type === "queue-updated") return true;
if (taskId.length === 0) return true;
if (!previousTask) return type !== "task-step";
if (event?.queueId && String(event.queueId) !== taskQueueLabel(previousTask)) return true;
if (event?.status && String(event.status) !== String(previousTask?.status || "")) return true;
return type !== "task-step" && String(event?.reason || "") !== "output";
}
function scheduleEventDrivenOverviewRefresh(): void {
if (!service || !isDocumentVisible()) return;
if (eventRefreshTimerRef.current !== null) window.clearTimeout(eventRefreshTimerRef.current);
eventRefreshTimerRef.current = window.setTimeout(() => {
eventRefreshTimerRef.current = null;
// SSE refreshes should update the sidebar data around the user's current
// selection, not jump to whichever task emitted the latest event.
void load(selectedIdRef.current, false).catch((err) => setError(errorText(err, "Code Queue 事件刷新失败")));
}, 120);
}
function scheduleSelectedTraceEventRefresh(taskId: string, includeSteps: boolean): void {
if (!service || !isDocumentVisible() || selectedIdRef.current !== taskId) return;
traceEventRefreshPendingStepsRef.current = traceEventRefreshPendingStepsRef.current || includeSteps;
if (traceEventRefreshTimerRef.current !== null) return;
traceEventRefreshTimerRef.current = window.setTimeout(() => {
traceEventRefreshTimerRef.current = null;
const refreshSteps = traceEventRefreshPendingStepsRef.current;
traceEventRefreshPendingStepsRef.current = false;
void ensureTraceSummary(taskId, true).catch((err) => setError(errorText(err, "事件刷新 Trace Summary 失败")));
if (refreshSteps) refreshLoadedTraceSteps(taskId);
}, 250);
}
function applyCodeQueueEvent(event: AnyRecord): void {
const taskId = String(event?.taskId || "");
const stepCount = nonNegativeCount(event?.stepCount);
const patch: AnyRecord = {};
const previousTask = taskId.length > 0
? taskRows(tasksDataRef.current).find((task: any) => String(task?.id || "") === taskId)
: null;
if (taskId.length > 0) {
if (event?.status) patch.status = String(event.status);
if (event?.updatedAt) patch.updatedAt = String(event.updatedAt);
if (event?.queueId) patch.queueId = String(event.queueId);
if (Number.isFinite(Number(event?.stepCount))) {
patch.stepCount = stepCount;
patch.llmStepCount = stepCount;
}
if (Object.keys(patch).length > 0 && (sessionCacheRef.current.has(taskId) || selectedIdRef.current === taskId)) {
publishCachedTask(taskId, patch, detailLoadTokenRef.current);
}
if (Object.keys(patch).length > 0) patchSidebarTaskFromEvent(taskId, patch);
if (selectedIdRef.current === taskId && (event?.type === "task-step" || event?.type === "task-updated")) {
scheduleSelectedTraceEventRefresh(taskId, event?.type === "task-step");
}
}
if (taskEventNeedsOverviewRefresh(event, taskId, previousTask)) scheduleEventDrivenOverviewRefresh();
}
useEffect(() => {
if (initialLoadSkippedRef.current) {
initialLoadSkippedRef.current = false;
@@ -2965,23 +3061,35 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
}, [service?.id, selectedQueueId]);
useEffect(() => {
if (!service) return undefined;
const tick = (): void => {
if (!isDocumentVisible()) return;
void load(selectedIdRef.current, false).catch((err) => setError(errorText(err, "Code Queue 轮询失败")));
if (!service || typeof EventSource === "undefined") return undefined;
const source = new EventSource(codexApi(apiBaseUrl, "/api/events"), { withCredentials: true });
const onEvent = (message: MessageEvent): void => {
try {
applyCodeQueueEvent(JSON.parse(String(message.data || "{}")));
} catch (err) {
setError(errorText(err, "解析 Code Queue 事件失败"));
}
};
const timer = window.setInterval(() => {
tick();
}, 1500);
const onVisible = (): void => {
if (isDocumentVisible()) tick();
if (isDocumentVisible()) scheduleEventDrivenOverviewRefresh();
};
source.addEventListener("task-step", onEvent);
source.addEventListener("task-updated", onEvent);
source.addEventListener("queue-updated", onEvent);
document.addEventListener("visibilitychange", onVisible);
return () => {
window.clearInterval(timer);
source.close();
document.removeEventListener("visibilitychange", onVisible);
if (eventRefreshTimerRef.current !== null) {
window.clearTimeout(eventRefreshTimerRef.current);
eventRefreshTimerRef.current = null;
}
if (traceEventRefreshTimerRef.current !== null) {
window.clearTimeout(traceEventRefreshTimerRef.current);
traceEventRefreshTimerRef.current = null;
}
};
}, [service?.id, selectedQueueId]);
}, [service?.id, apiBaseUrl, selectedQueueId]);
useEffect(() => {
if (!service || !selectedTask || selectedDetailLoading) return;
@@ -2989,7 +3097,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
if (!taskId) return;
const updatedAt = String(selectedTask.updatedAt || "");
if (traceSummaryIsCurrent(selectedTask)) return;
const key = `${taskId}:${updatedAt || "unknown"}:${rawTaskStepCount(selectedTask)}:${traceSummaryStepCount(taskTraceSummary(selectedTask))}`;
const key = `${taskId}:${updatedAt || "unknown"}:${String(selectedTask?._traceSummaryUpdatedAt || "")}`;
if (autoTraceLoadKeysRef.current.has(key)) return;
autoTraceLoadKeysRef.current.add(key);
void ensureTraceSummary(taskId, true).catch((err) => setError(errorText(err, "自动加载 Trace Summary 失败")));
@@ -3188,6 +3296,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
queueRows.map((row: any) => h("option", { key: String(row?.id || ""), value: String(row?.id || "") }, queueOptionLabel(row))),
),
h("button", { type: "button", className: "ghost-btn codex-rename-queue-btn", onClick: () => void renameQueue(), disabled: busy || submitting || !queueId, title: "修改当前 queue 的显示名称,ID 不变", "data-testid": "codex-rename-queue-button" }, "改名"),
h("button", { type: "button", className: "ghost-btn codex-merge-queue-btn", onClick: () => void mergeQueue(), disabled: busy || submitting || queueRows.length < 2, title: "把其他 queue 合并到当前 queue;源 queue 记录会保留,不会删除", "data-testid": "codex-merge-queue-button" }, "合并 queue"),
h("button", { type: "button", className: "ghost-btn codex-create-queue-btn", onClick: () => void createQueue(), disabled: busy || submitting, "data-testid": "codex-create-queue-button" }, "创建 queue"),
),
),
+13 -3
View File
@@ -707,9 +707,12 @@ async function proxyCodeQueueDirect(req: Request, url: URL): Promise<Response> {
}
const overviewCacheKey = `${suffix}${url.search}`;
const canUseOverviewCache = req.method === "GET" && suffix === "/api/tasks/overview";
const expectsJsonResponse = suffix === "/health" || suffix.startsWith("/api/");
const isEventStreamRequest = req.method === "GET" && suffix === "/api/events";
const cacheControl = req.headers.get("cache-control") || "";
const bypassOverviewCache = /\bno-cache\b|\bno-store\b/iu.test(cacheControl) || req.headers.get("x-unidesk-no-cache") === "1";
const expectsJsonResponse = !isEventStreamRequest && (suffix === "/health" || suffix.startsWith("/api/"));
if (req.method !== "GET" && req.method !== "HEAD") invalidateCodeQueueOverviewCache();
if (canUseOverviewCache) {
if (canUseOverviewCache && !bypassOverviewCache) {
const cached = cachedCodeQueueOverview(overviewCacheKey);
if (cached !== null) {
recordOperationPerformance("frontend", "code_queue_direct_proxy_cache", 0, true, overviewCacheKey);
@@ -733,6 +736,13 @@ async function proxyCodeQueueDirect(req: Request, url: URL): Promise<Response> {
const responseHeaders = new Headers();
const upstreamContentType = upstream.headers.get("content-type");
if (upstreamContentType !== null) responseHeaders.set("content-type", upstreamContentType);
if (isEventStreamRequest) {
responseHeaders.set("cache-control", upstream.headers.get("cache-control") || "no-store, no-transform");
responseHeaders.set("connection", "keep-alive");
const buffering = upstream.headers.get("x-accel-buffering");
if (buffering !== null) responseHeaders.set("x-accel-buffering", buffering);
return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
}
const upstreamBody = await upstream.arrayBuffer();
const isJsonResponse = (upstreamContentType ?? "").toLowerCase().includes("json");
let parsedJson: unknown = null;
@@ -786,7 +796,7 @@ async function proxyCodeQueueDirect(req: Request, url: URL): Promise<Response> {
}
if (expectsJsonResponse) {
const text = jsonText;
if (canUseOverviewCache && upstream.ok && typeof parsedJson === "object" && parsedJson !== null) {
if (canUseOverviewCache && !bypassOverviewCache && upstream.ok && typeof parsedJson === "object" && parsedJson !== null) {
codeQueueOverviewCache.set(codeQueueOverviewCacheKey(overviewCacheKey), { at: Date.now(), payload: parsedJson as JsonValue, text });
}
return new Response(text, { status: upstream.status, headers: responseHeaders });