feat: freeze legacy code queue entry

This commit is contained in:
Codex
2026-06-09 01:14:23 +00:00
parent 7306000ac1
commit 987f41d258
9 changed files with 389 additions and 103 deletions
+43
View File
@@ -2754,6 +2754,49 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
opacity: 0.64;
cursor: wait;
}
.codex-task-form.is-frozen textarea,
.codex-task-form.is-frozen input,
.codex-task-form.is-frozen select {
opacity: 0.72;
}
.codex-task-form.is-frozen .primary-btn {
cursor: not-allowed;
}
.codex-freeze-notice {
display: grid;
gap: 6px;
padding: 10px;
border: 1px solid rgba(215, 161, 58, 0.36);
border-radius: 8px;
background:
linear-gradient(135deg, rgba(215, 161, 58, 0.10), rgba(78, 183, 168, 0.05)),
rgba(7, 16, 20, 0.72);
color: rgba(217, 238, 238, 0.78);
font-size: 12px;
line-height: 1.45;
}
.codex-freeze-notice strong {
color: var(--text);
font-size: 13px;
}
.codex-freeze-notice p {
margin: 0;
}
.codex-freeze-command-grid {
display: grid;
gap: 4px;
}
.codex-freeze-command-grid code {
display: block;
max-width: 100%;
padding: 5px 7px;
overflow-wrap: anywhere;
white-space: normal;
border: 1px solid rgba(78, 183, 168, 0.24);
border-radius: 6px;
background: rgba(0,0,0,0.22);
color: #dff8f3;
}
.codex-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
+42 -32
View File
@@ -2087,19 +2087,19 @@ function CodexStatsPanel({ stats, queueName: activeQueueName, onRaw }: AnyRecord
);
}
function TaskQueueMoveControl({ task, queueRows, busy, onMove }: AnyRecord) {
function TaskQueueMoveControl({ task, queueRows, busy, frozen, onMove }: AnyRecord) {
const taskId = String(task?.id || "");
const currentQueue = taskQueueLabel(task);
const [targetQueue, setTargetQueue] = useState(currentQueue);
useEffect(() => {
setTargetQueue(currentQueue);
}, [taskId, currentQueue]);
const disabled = !taskId || busy || ["running", "judging", "retry_wait"].includes(String(task?.status || ""));
const disabled = frozen || !taskId || busy || ["running", "judging", "retry_wait"].includes(String(task?.status || ""));
return h("div", { className: "codex-task-move-control", "data-testid": "codex-task-queue-move-control" },
h("label", null, "任务 queue",
h("select", {
value: targetQueue,
disabled: !taskId || busy,
disabled: frozen || !taskId || busy,
onChange: (event: any) => setTargetQueue(String(event.target.value || currentQueue)),
"data-testid": "codex-task-queue-move-select",
},
@@ -2111,7 +2111,7 @@ function TaskQueueMoveControl({ task, queueRows, busy, onMove }: AnyRecord) {
className: "ghost-btn",
disabled: disabled || targetQueue === currentQueue,
onClick: () => onMove(targetQueue),
title: disabled ? "运行中 / judging / retry_wait 的任务不能移动;请先打断或等当前 turn 结束" : "移动已创建任务到另一个 queue",
title: frozen ? "旧 Code Queue 任务移动已冻结" : disabled ? "运行中 / judging / retry_wait 的任务不能移动;请先打断或等当前 turn 结束" : "移动已创建任务到另一个 queue",
"data-testid": "codex-task-queue-move-button",
}, "移动"),
);
@@ -2584,7 +2584,8 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
}, [promptParts, repeatCount, referenceTaskId]);
const enqueueCount = enqueueItems.length;
const batchNeedsConfirmation = enqueueCount > 1 && !batchConfirmed;
const submitDisabled = submitting || busy || enqueueCount === 0 || batchNeedsConfirmation;
const legacyCodeQueueFrozen = true;
const submitDisabled = legacyCodeQueueFrozen || submitting || busy || enqueueCount === 0 || batchNeedsConfirmation;
const codexModels = codexModelOptions(queue, model);
const providerOptions = codexProviderOptions(queue, providerId);
const executionModeRows = codexExecutionModeOptions(queue, executionMode);
@@ -2592,10 +2593,10 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
const savedWorkdirs = Array.isArray(workdirData?.workdirs) ? workdirData.workdirs : [];
const currentWorkdirOptions = workdirOptions(queue, savedWorkdirs, providerId, executionMode, cwd);
const currentWorkdirSaved = currentWorkdirOptions.some((item: any) => item.source === "saved" && normalizedWorkdirPath(item.path) === normalizedWorkdirPath(cwd));
const selectedCanSteer = selectedTask?.id && selectedTask?.activeTurnId && String(selectedTask?.status) === "running";
const selectedCanSteer = !legacyCodeQueueFrozen && selectedTask?.id && selectedTask?.activeTurnId && String(selectedTask?.status) === "running";
const selectedCanInterrupt = selectedTask?.id && !["succeeded", "failed", "canceled"].includes(String(selectedTask?.status || ""));
const selectedCanRetry = selectedTask?.id && ["succeeded", "failed", "canceled"].includes(String(selectedTask?.status || ""));
const selectedCanEditPrompt = selectedTask?.id && taskPromptEditable(selectedTask);
const selectedCanRetry = !legacyCodeQueueFrozen && selectedTask?.id && ["succeeded", "failed", "canceled"].includes(String(selectedTask?.status || ""));
const selectedCanEditPrompt = !legacyCodeQueueFrozen && selectedTask?.id && taskPromptEditable(selectedTask);
function setTasksData(nextOrUpdater: any): any {
const next = typeof nextOrUpdater === "function" ? nextOrUpdater(tasksDataRef.current) : nextOrUpdater;
@@ -4268,12 +4269,12 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
onClose: () => setMergeDialogOpen(false),
actions: [
h("button", { key: "cancel", type: "button", className: "ghost-btn", onClick: () => setMergeDialogOpen(false), disabled: busy, "data-testid": "codex-merge-queue-cancel" }, "取消"),
h("button", { key: "confirm", type: "button", className: "primary-btn", onClick: () => void mergeQueue(), disabled: busy || submitting || !mergeSourceQueueId, "data-testid": "codex-merge-queue-confirm" }, busy ? "合并中..." : "确认合并"),
h("button", { key: "confirm", type: "button", className: "primary-btn", onClick: () => void mergeQueue(), disabled: legacyCodeQueueFrozen || busy || submitting || !mergeSourceQueueId, "data-testid": "codex-merge-queue-confirm" }, legacyCodeQueueFrozen ? "旧入口已冻结" : busy ? "合并中..." : "确认合并"),
],
},
h("p", { className: "codex-merge-dialog-target" }, "目标 queue", h("code", null, queueOptionLabel(mergeTargetQueueRow))),
h("label", null, "源 queue",
h("select", { value: mergeSourceQueueId, disabled: busy || submitting, onChange: (event: any) => setMergeSourceQueueId(String(event.target.value || "")), "data-testid": "codex-merge-source-queue-select" },
h("select", { value: mergeSourceQueueId, disabled: legacyCodeQueueFrozen || busy || submitting, onChange: (event: any) => setMergeSourceQueueId(String(event.target.value || "")), "data-testid": "codex-merge-source-queue-select" },
h("option", { value: "" }, "选择要合并进来的源 queue"),
mergeSourceQueueRows.map((row: any) => h("option", { key: String(row?.id || ""), value: String(row?.id || "") }, queueOptionLabel(row))),
),
@@ -4300,65 +4301,74 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
),
h("div", { className: "code-queue-layout" },
h("div", { className: "codex-left-rail" },
h(Panel, { title: "提交任务", eyebrow: submitting ? "Submitting..." : enqueueItems.length > 1 ? `${enqueueItems.length} tasks` : "Single or Batch", className: "codex-compose-panel", loading: submitting },
h("form", { className: `codex-task-form ${submitting ? "is-submitting" : ""}`, onSubmit: enqueue, "data-testid": "code-queue-task-form", "aria-busy": submitting ? "true" : "false" },
h(Panel, { title: "旧 Code Queue 入口", eyebrow: "Frozen / Archive", className: "codex-compose-panel", loading: submitting },
h("form", { className: `codex-task-form ${submitting ? "is-submitting" : ""} ${legacyCodeQueueFrozen ? "is-frozen" : ""}`, onSubmit: enqueue, "data-testid": "code-queue-task-form", "aria-busy": submitting ? "true" : "false", "aria-disabled": legacyCodeQueueFrozen ? "true" : "false" },
h("div", { className: "codex-freeze-notice", "data-testid": "code-queue-freeze-notice" },
h("strong", null, "旧 Code Queue 新任务入口已冻结"),
h("p", null, "新任务请使用 AgentRun Queue;本页保留历史任务、Trace 和排障查看。"),
h("div", { className: "codex-freeze-command-grid" },
h("code", null, "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli"),
h("code", null, "bun scripts/cli.ts agentrun v01 queue submit --json-file <task.json>"),
h("code", null, "bun scripts/cli.ts agentrun v01 sessions trace <sessionId> --after-seq 0 --limit 100"),
),
),
h("label", null, "Prompt / 多任务用单独一行 --- 分隔",
h("textarea", { value: prompt, rows: 8, disabled: submitting, onChange: (event: any) => setPrompt(event.target.value), placeholder: "写入 Codex 任务;多个任务之间用 --- 分隔。" }),
h("textarea", { value: prompt, rows: 8, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setPrompt(event.target.value), placeholder: "旧入口已冻结;新任务使用 AgentRun Queue。" }),
),
h("label", { className: "codex-reference-field" }, "引用任务 ID(可选)",
h("input", { value: referenceTaskId, disabled: submitting, onChange: (event: any) => setReferenceTaskId(event.target.value), placeholder: "codex_...;支持空格/逗号分隔多个 ID", "data-testid": "codex-reference-task-id" }),
h("input", { value: referenceTaskId, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setReferenceTaskId(event.target.value), placeholder: "codex_...;支持空格/逗号分隔多个 ID", "data-testid": "codex-reference-task-id" }),
parseReferenceTaskIds(referenceTaskId).length > 0 ? h("code", null, `后端将解析并注入:${parseReferenceTaskIds(referenceTaskId).join(" / ")}`) : null,
),
h("div", { className: "codex-form-grid" },
h("label", { className: "codex-submit-queue-field" }, "Queue",
h("div", { className: "codex-submit-queue-row" },
h("select", { className: "codex-submit-queue-select", value: queueId, disabled: submitting, onChange: (event: any) => setQueueId(String(event.target.value || "default")), "data-testid": "code-queue-id-select" },
h("select", { className: "codex-submit-queue-select", value: queueId, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setQueueId(String(event.target.value || "default")), "data-testid": "code-queue-id-select" },
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: () => openMergeQueueDialog(), disabled: busy || submitting || mergeSourceQueueRows.length === 0, 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"),
h("button", { type: "button", className: "ghost-btn codex-rename-queue-btn", onClick: () => void renameQueue(), disabled: legacyCodeQueueFrozen || busy || submitting || !queueId, title: legacyCodeQueueFrozen ? "旧 Code Queue queue 管理已冻结" : "修改当前 queue 的显示名称,ID 不变", "data-testid": "codex-rename-queue-button" }, "改名"),
h("button", { type: "button", className: "ghost-btn codex-merge-queue-btn", onClick: () => openMergeQueueDialog(), disabled: legacyCodeQueueFrozen || busy || submitting || mergeSourceQueueRows.length === 0, title: legacyCodeQueueFrozen ? "旧 Code Queue queue 管理已冻结" : "打开独立窗口选择源 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: legacyCodeQueueFrozen || busy || submitting, "data-testid": "codex-create-queue-button" }, "创建 queue"),
),
),
h("label", null, "模型",
h("select", { value: model, disabled: submitting, onChange: (event: any) => setModel(event.target.value), "data-testid": "codex-model-select" },
h("select", { value: model, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setModel(event.target.value), "data-testid": "codex-model-select" },
codexModels.map((name) => h("option", { key: name, value: name }, name)),
),
),
h("label", null, "执行 Provider",
h("select", { value: providerId, disabled: submitting, onChange: (event: any) => changeSubmitProvider(String(event.target.value || "D601")), "data-testid": "codex-provider-select" },
h("select", { value: providerId, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => changeSubmitProvider(String(event.target.value || "D601")), "data-testid": "codex-provider-select" },
providerOptions.map((provider: any) => h("option", { key: provider.id, value: provider.id }, `${provider.label || provider.id} · ${provider.defaultWorkdir || providerDefaultWorkdir(queue, provider.id)}${provider.supportsWindowsNativeCodex ? " · Windows native" : ""}`)),
),
),
h("label", null, "执行模式",
h("select", { value: executionMode, disabled: submitting, onChange: (event: any) => changeSubmitExecutionMode(String(event.target.value || "default")), "data-testid": "codex-execution-mode-select" },
h("select", { value: executionMode, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => changeSubmitExecutionMode(String(event.target.value || "default")), "data-testid": "codex-execution-mode-select" },
executionModeRows.map((mode: any) => h("option", { key: mode.id, value: mode.id }, `${mode.label || mode.id}${mode.id === "windows-native" ? " · 宿主 Codex" : ""}`)),
),
),
h("label", { className: "codex-workdir-field" }, "工作目录",
h("div", { className: "codex-workdir-row" },
h("input", { value: cwd, disabled: submitting, onChange: (event: any) => setCwd(event.target.value), placeholder: currentProviderDefaultWorkdir || queue?.defaultWorkdir || "/workspace", "data-testid": "codex-cwd-input" }),
h("select", { value: normalizedWorkdirPath(cwd), disabled: submitting || workdirBusy, onChange: (event: any) => setCwd(String(event.target.value || "")), "data-testid": "codex-cwd-select" },
h("input", { value: cwd, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setCwd(event.target.value), placeholder: currentProviderDefaultWorkdir || queue?.defaultWorkdir || "/workspace", "data-testid": "codex-cwd-input" }),
h("select", { value: normalizedWorkdirPath(cwd), disabled: legacyCodeQueueFrozen || submitting || workdirBusy, onChange: (event: any) => setCwd(String(event.target.value || "")), "data-testid": "codex-cwd-select" },
currentWorkdirOptions.map((item: any) => h("option", { key: `${item.providerId}:${item.executionMode}:${item.path}`, value: item.path }, `${item.path}${item.source === "default" ? " · 默认" : ""}`)),
),
h("button", { type: "button", className: "ghost-btn codex-workdir-create-btn", disabled: submitting || busy || workdirBusy, onClick: () => void createWorkdirPreset(), "data-testid": "codex-cwd-create-button" }, workdirBusy ? "处理中" : "新建"),
h("button", { type: "button", className: "ghost-btn codex-workdir-delete-btn", disabled: submitting || busy || workdirBusy || !currentWorkdirSaved, onClick: () => void deleteWorkdirPreset(), title: currentWorkdirSaved ? "从工作目录下拉菜单删除这个选项,不删除磁盘目录" : "当前工作目录尚未保存到下拉菜单", "data-testid": "codex-cwd-delete-button" }, "删除"),
h("button", { type: "button", className: "ghost-btn codex-workdir-create-btn", disabled: legacyCodeQueueFrozen || submitting || busy || workdirBusy, onClick: () => void createWorkdirPreset(), "data-testid": "codex-cwd-create-button" }, workdirBusy ? "处理中" : "新建"),
h("button", { type: "button", className: "ghost-btn codex-workdir-delete-btn", disabled: legacyCodeQueueFrozen || submitting || busy || workdirBusy || !currentWorkdirSaved, onClick: () => void deleteWorkdirPreset(), title: legacyCodeQueueFrozen ? "旧 Code Queue 工作目录管理已冻结" : currentWorkdirSaved ? "从工作目录下拉菜单删除这个选项,不删除磁盘目录" : "当前工作目录尚未保存到下拉菜单", "data-testid": "codex-cwd-delete-button" }, "删除"),
),
),
h("label", null, "最大尝试", h("input", { type: "number", min: 1, max: 99, value: maxAttempts, disabled: submitting, onChange: (event: any) => setMaxAttempts(Number(event.target.value)), "data-testid": "codex-max-attempts-input" })),
h("label", null, "入队份数", h("input", { type: "number", min: 1, max: 50, value: repeatCount, disabled: submitting, onChange: (event: any) => setRepeatCount(Number(event.target.value)), "data-testid": "codex-repeat-count-input" })),
h("label", null, "最大尝试", h("input", { type: "number", min: 1, max: 99, value: maxAttempts, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setMaxAttempts(Number(event.target.value)), "data-testid": "codex-max-attempts-input" })),
h("label", null, "入队份数", h("input", { type: "number", min: 1, max: 50, value: repeatCount, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setRepeatCount(Number(event.target.value)), "data-testid": "codex-repeat-count-input" })),
),
enqueueCount > 1 ? h("label", { className: `codex-batch-confirm ${batchConfirmed ? "confirmed" : ""}`, "data-testid": "codex-batch-confirm-row" },
h("input", {
type: "checkbox",
checked: batchConfirmed,
disabled: submitting,
disabled: legacyCodeQueueFrozen || submitting,
onChange: (event: any) => setBatchConfirmed(Boolean(event.target.checked)),
"data-testid": "codex-batch-confirm-checkbox",
}),
h("span", null, `确认批量入队 ${enqueueCount} 个任务(prompt 分段 ${promptParts.length} × 入队份数 ${repeatCountValue(repeatCount)}`),
) : null,
submitting ? h("div", { className: "codex-submit-wait", "data-testid": "codex-submit-wait" }, "正在提交到后端,已锁定输入以防重复提交...") : null,
legacyCodeQueueFrozen || submitting ? h("div", { className: "codex-submit-wait", "data-testid": "codex-submit-wait" }, legacyCodeQueueFrozen ? "旧入口冻结:提交、重试、steer、queue 管理不会再写入旧 Code Queue。" : "正在提交到后端,已锁定输入以防重复提交...") : null,
h("div", { className: "codex-form-actions" },
h("button", {
type: "button",
@@ -4374,16 +4384,16 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi
},
"data-testid": "codex-clear-input-button",
}, "清空输入"),
h("button", { type: "submit", className: "primary-btn", disabled: submitDisabled, "data-testid": "codex-enqueue-button" }, submitting ? "提交中,请等待..." : batchNeedsConfirmation ? `请确认批量入队 ${enqueueCount} 个任务` : enqueueItems.length > 1 ? `批量入队 ${enqueueItems.length} 个任务` : "入队并运行"),
h("button", { type: "submit", className: "primary-btn", disabled: submitDisabled, "data-testid": "codex-enqueue-button" }, legacyCodeQueueFrozen ? "已冻结,使用 AgentRun Queue" : submitting ? "提交中,请等待..." : batchNeedsConfirmation ? `请确认批量入队 ${enqueueCount} 个任务` : enqueueItems.length > 1 ? `批量入队 ${enqueueItems.length} 个任务` : "入队并运行"),
),
),
),
),
h("div", { className: "codex-main-stage" },
h("div", { className: "codex-detail-grid" },
h(Panel, { title: "运行控制", eyebrow: selectedCanEditPrompt ? "Queued prompt editable" : selectedCanSteer ? "Active turn steer" : "Steer when running", loading: busy },
h(Panel, { title: "运行控制", eyebrow: legacyCodeQueueFrozen ? "Legacy write controls frozen" : selectedCanEditPrompt ? "Queued prompt editable" : selectedCanSteer ? "Active turn steer" : "Steer when running", loading: busy },
h("div", { className: "codex-run-control-stack" },
h(TaskQueueMoveControl, { task: selectedTask, queueRows, busy, onMove: moveSelectedTaskQueue }),
h(TaskQueueMoveControl, { task: selectedTask, queueRows, busy, frozen: legacyCodeQueueFrozen, onMove: moveSelectedTaskQueue }),
selectedTask?.id ? h("form", { className: "codex-steer-form codex-edit-prompt-form", onSubmit: editSelectedQueuedPrompt, "data-testid": "codex-edit-prompt-form" },
h("label", null, "编辑 queued 用户 prompt",
h("textarea", {