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", {
@@ -4859,6 +4859,48 @@ function readOnlyRejectResponse(method: string, targetPath: string): Response {
}, 405);
}
function legacyQueueFrozenResponse(method: string, targetPath: string, action: string): Response {
return jsonResponse({
ok: false,
frozen: true,
mutation: false,
error: "Legacy UniDesk Code Queue is frozen for new task and execution mutations; use AgentRun Queue for new commander work.",
reason: "legacy-code-queue-frozen",
serviceRole: config.serviceRole,
method,
path: targetPath,
action,
replacement: {
queueCommander: "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli",
queueSubmit: "bun scripts/cli.ts agentrun v01 queue submit --json-file <task.json>",
queueDispatch: "bun scripts/cli.ts agentrun v01 queue dispatch <taskId> --json-file <dispatch.json>",
sessionsTrace: "bun scripts/cli.ts agentrun v01 sessions trace <sessionId> --after-seq 0 --limit 100",
sessionsOutput: "bun scripts/cli.ts agentrun v01 sessions output <sessionId> --after-seq 0 --limit 100",
sessionsSteer: "bun scripts/cli.ts agentrun v01 sessions steer <sessionId> --prompt-file <path>",
sessionsRead: "bun scripts/cli.ts agentrun v01 sessions read <sessionId> --reader-id cli",
queueCancel: "bun scripts/cli.ts agentrun v01 queue cancel <taskId> --reason <text>",
},
legacy: {
retainedFor: "historical archive/read-only troubleshooting",
allowedReadPaths: [
"GET /api/tasks",
"GET /api/tasks/overview",
"GET /api/tasks/:id",
"GET /api/tasks/:id/output",
"GET /api/tasks/:id/trace-summary",
"GET /api/tasks/:id/trace-steps",
"GET /api/queues",
],
cancellationPath: "POST /api/tasks/:id/interrupt remains available only to stop residual old runs",
noDoubleWrite: true,
},
}, 410);
}
function legacyQueueWritesFrozen(): boolean {
return true;
}
function databaseNotReadyResponse(method: string, targetPath: string): Response {
return jsonResponse({
ok: false,
@@ -4918,6 +4960,7 @@ async function listWorkdirs(url: URL): Promise<Response> {
}
async function createWorkdir(req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, "/api/workdirs", "workdir-create");
const notReady = requireDatabaseReadyForWrite(req.method, "/api/workdirs");
if (notReady !== null) return notReady;
const body = await readJson(req);
@@ -4948,6 +4991,7 @@ async function createWorkdir(req: Request): Promise<Response> {
}
async function deleteWorkdir(providerIdValue: string, executionModeValue: string, pathValue: string): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse("DELETE", `/api/workdirs/${providerIdValue}/${executionModeValue}/${pathValue}`, "workdir-delete");
const notReady = requireDatabaseReadyForWrite("DELETE", `/api/workdirs/${providerIdValue}/${executionModeValue}/${pathValue}`);
if (notReady !== null) return notReady;
const providerId = normalizeTaskProviderId(providerIdValue);
@@ -4970,6 +5014,7 @@ async function deleteWorkdir(providerIdValue: string, executionModeValue: string
}
async function createTasks(req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, "/api/tasks", "task-submit");
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/tasks");
const notReady = requireDatabaseReadyForWrite(req.method, "/api/tasks");
if (notReady !== null) return notReady;
@@ -4998,6 +5043,7 @@ async function createTasks(req: Request): Promise<Response> {
}
async function steerTask(task: QueueTask, req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/steer`, "task-steer");
if (!serviceRoleAllowsScheduler(config.serviceRole)) return schedulerOnlyRejectResponse(req.method, `/api/tasks/${task.id}/steer`);
const body = await readJson(req);
const bodyRecord = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
@@ -5062,6 +5108,7 @@ async function steerTask(task: QueueTask, req: Request): Promise<Response> {
}
async function resumeTask(task: QueueTask, req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/resume`, "task-resume");
if (!serviceRoleAllowsScheduler(config.serviceRole)) return schedulerOnlyRejectResponse(req.method, `/api/tasks/${task.id}/resume`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/resume`);
if (notReady !== null) return notReady;
@@ -5163,6 +5210,7 @@ async function resumeTask(task: QueueTask, req: Request): Promise<Response> {
}
async function editQueuedTaskPrompt(task: QueueTask, req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/edit`, "queued-prompt-edit");
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/edit`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/edit`);
if (notReady !== null) return notReady;
@@ -5238,6 +5286,7 @@ async function interruptTask(task: QueueTask, method = "POST"): Promise<Response
}
async function manualRetry(task: QueueTask, req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/retry`, "task-retry");
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/retry`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/retry`);
if (notReady !== null) return notReady;
@@ -5399,6 +5448,7 @@ async function markTerminalTasksRead(url: URL): Promise<Response> {
}
async function createQueue(req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, "/api/queues", "queue-create");
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/queues");
const notReady = requireDatabaseReadyForWrite(req.method, "/api/queues");
if (notReady !== null) return notReady;
@@ -5418,6 +5468,7 @@ async function createQueue(req: Request): Promise<Response> {
}
async function updateQueue(queueIdValue: string, req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/queues/${queueIdValue}`, "queue-update");
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/queues/${queueIdValue}`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/queues/${queueIdValue}`);
if (notReady !== null) return notReady;
@@ -5628,6 +5679,7 @@ function deleteQueuesFromState(queueIds: string[]): QueueRecord[] {
}
async function mergeQueues(targetQueueIdValue: string | null, req: Request): Promise<Response> {
if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`, "queue-merge");
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`);
const mergePath = targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`;
const notReady = requireDatabaseReadyForWrite(req.method, mergePath);
@@ -5726,6 +5778,7 @@ async function mergeQueues(targetQueueIdValue: string | null, req: Request): Pro
}
async function moveTaskToQueue(task: QueueTask, req: Request, options: { bypassRoleCheck?: boolean } = {}): Promise<Response> {
if (options.bypassRoleCheck !== true && legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/move`, "task-move");
if (options.bypassRoleCheck !== true && !serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/move`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/move`);
if (notReady !== null) return notReady;