102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { NextRequest } from "next/server";
|
|
import { jsonNoStore } from "@/lib/api-response";
|
|
import { requireRequestSession } from "@/lib/boss-auth";
|
|
import { requireCsrfSafeMutation } from "@/lib/boss-csrf";
|
|
import {
|
|
canRetryMasterAgentTaskSafely,
|
|
getMasterAgentTask,
|
|
type MasterAgentTask,
|
|
retryRecoverableMasterAgentTask,
|
|
} from "@/lib/boss-data";
|
|
|
|
function stringValue(value: unknown) {
|
|
return typeof value === "string" ? value.trim() : "";
|
|
}
|
|
|
|
async function paramsTaskId(context: { params: Promise<{ taskId: string }> }) {
|
|
const params = await context.params;
|
|
return params.taskId;
|
|
}
|
|
|
|
function forbidden() {
|
|
return jsonNoStore({ ok: false, message: "FORBIDDEN" }, { status: 403 });
|
|
}
|
|
|
|
function recoveryProjection(task: MasterAgentTask) {
|
|
const phase = task.phase ?? task.status;
|
|
const lastProgressAt = task.lastProgressAt ?? task.claimedAt ?? task.requestedAt;
|
|
const canRetry = canRetryMasterAgentTaskSafely(task);
|
|
return {
|
|
taskId: task.taskId,
|
|
projectId: task.projectId,
|
|
deviceId: task.deviceId,
|
|
status: task.status,
|
|
phase,
|
|
canRetry,
|
|
safeNextAction: canRetry ? "retry" : task.status === "needs_user_action" ? "user_action" : "inspect",
|
|
diagnosis: `任务处于 ${phase},最后进度时间 ${lastProgressAt}`,
|
|
lastErrorCode: task.lastErrorCode,
|
|
lastProgressAt,
|
|
};
|
|
}
|
|
|
|
export async function GET(
|
|
request: NextRequest,
|
|
context: { params: Promise<{ taskId: string }> },
|
|
) {
|
|
const session = await requireRequestSession(request);
|
|
if (!session) {
|
|
return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
|
}
|
|
|
|
const taskId = await paramsTaskId(context);
|
|
const task = await getMasterAgentTask(taskId);
|
|
if (!task) {
|
|
return jsonNoStore({ ok: false, message: "MASTER_AGENT_TASK_NOT_FOUND" }, { status: 404 });
|
|
}
|
|
if (session.role !== "highest_admin" && task.requestedByAccount !== session.account) {
|
|
return forbidden();
|
|
}
|
|
|
|
return jsonNoStore({
|
|
ok: true,
|
|
recovery: recoveryProjection(task),
|
|
});
|
|
}
|
|
|
|
export async function POST(
|
|
request: NextRequest,
|
|
context: { params: Promise<{ taskId: string }> },
|
|
) {
|
|
const csrf = requireCsrfSafeMutation(request);
|
|
if (csrf) return csrf;
|
|
|
|
const session = await requireRequestSession(request);
|
|
if (!session) {
|
|
return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
|
}
|
|
if (session.role !== "highest_admin") {
|
|
return forbidden();
|
|
}
|
|
|
|
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
|
|
const action = stringValue(body.action);
|
|
if (action !== "retry") {
|
|
return jsonNoStore({ ok: false, message: "TASK_RECOVERY_ACTION_INVALID" }, { status: 400 });
|
|
}
|
|
|
|
const taskId = await paramsTaskId(context);
|
|
try {
|
|
const task = await retryRecoverableMasterAgentTask({
|
|
taskId,
|
|
actorAccount: session.account,
|
|
reason: stringValue(body.reason) || "管理员从恢复面板重试任务",
|
|
});
|
|
return jsonNoStore({ ok: true, action, task: recoveryProjection(task) });
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "TASK_RECOVERY_FAILED";
|
|
const status = message === "MASTER_AGENT_TASK_NOT_FOUND" ? 404 : 400;
|
|
return jsonNoStore({ ok: false, message }, { status });
|
|
}
|
|
}
|