feat: freeze legacy code queue entry
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user