feat: add dispatch retry and import recovery flows
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { requireRequestSession } from "@/lib/boss-auth";
|
||||
import { appendProjectMessage, readState } from "@/lib/boss-data";
|
||||
import { queueGroupDispatchPlan } from "@/lib/boss-master-agent";
|
||||
|
||||
function buildCollaborationGate(project?: {
|
||||
isGroup: boolean;
|
||||
collaborationMode: "development" | "approval_required";
|
||||
approvalState: "not_required" | "pending_agent" | "pending_user" | "approved" | "rejected";
|
||||
}) {
|
||||
return project
|
||||
? {
|
||||
isGroup: project.isGroup,
|
||||
collaborationMode: project.collaborationMode,
|
||||
requiresMasterAgentApproval: project.isGroup && project.collaborationMode === "approval_required",
|
||||
approvalState: project.approvalState,
|
||||
}
|
||||
: {
|
||||
isGroup: false,
|
||||
collaborationMode: "development" as const,
|
||||
requiresMasterAgentApproval: false,
|
||||
approvalState: "not_required" as const,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchFailureNotice(error?: string) {
|
||||
switch (error) {
|
||||
case "GROUP_DISPATCH_TARGETS_REQUIRED":
|
||||
return "当前群聊里还没有可下发的真实线程,请先在群资料里重新添加线程后再试。";
|
||||
case "DISPATCH_TARGET_PROJECT_NOT_FOUND":
|
||||
return "当前群聊里有失效的线程引用,请重新整理群成员后再试。";
|
||||
default:
|
||||
return error ? `主 Agent 暂时无法重新生成推荐:${error}` : "主 Agent 暂时无法重新生成推荐,请稍后重试。";
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ projectId: string; planId: string }> },
|
||||
) {
|
||||
const session = await requireRequestSession(request);
|
||||
if (!session) {
|
||||
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { projectId, planId } = await context.params;
|
||||
try {
|
||||
const state = await readState();
|
||||
const project = state.projects.find((item) => item.id === projectId);
|
||||
if (!project) {
|
||||
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
|
||||
}
|
||||
if (!project.isGroup) {
|
||||
return NextResponse.json({ ok: false, message: "PROJECT_NOT_GROUP_CHAT" }, { status: 400 });
|
||||
}
|
||||
|
||||
const plan = state.dispatchPlans.find((item) => item.planId === planId);
|
||||
if (!plan || plan.groupProjectId !== projectId) {
|
||||
return NextResponse.json({ ok: false, message: "DISPATCH_PLAN_NOT_FOUND" }, { status: 404 });
|
||||
}
|
||||
if (plan.status !== "rejected") {
|
||||
return NextResponse.json({ ok: false, message: "DISPATCH_PLAN_NOT_REJECTED" }, { status: 400 });
|
||||
}
|
||||
|
||||
const pendingPlan = [...state.dispatchPlans]
|
||||
.filter(
|
||||
(item) => item.groupProjectId === projectId && item.status === "pending_user_confirmation",
|
||||
)
|
||||
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
|
||||
if (pendingPlan) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
ok: false,
|
||||
message: "当前还有一条主 Agent 推荐等待你确认,请先确认或拒绝后再继续。",
|
||||
pendingPlan,
|
||||
collaborationGate: buildCollaborationGate(project),
|
||||
},
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
const requestMessage = project.messages.find((message) => message.id === plan.requestMessageId);
|
||||
const requestText = requestMessage?.body?.trim() || plan.summary?.trim();
|
||||
if (!requestText) {
|
||||
return NextResponse.json(
|
||||
{ ok: false, message: "DISPATCH_PLAN_REQUEST_TEXT_REQUIRED" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const retryMessageId = `${plan.requestMessageId}:retry:${Date.now()}`;
|
||||
const recommendation = await queueGroupDispatchPlan({
|
||||
groupProjectId: projectId,
|
||||
requestMessageId: retryMessageId,
|
||||
requestText,
|
||||
requestedBy: session.account,
|
||||
});
|
||||
|
||||
if (!recommendation.ok) {
|
||||
await appendProjectMessage({
|
||||
projectId,
|
||||
sender: "master",
|
||||
senderLabel: "主 Agent",
|
||||
body: dispatchFailureNotice(recommendation.error),
|
||||
kind: "system_notice",
|
||||
});
|
||||
}
|
||||
|
||||
const nextState = await readState();
|
||||
const nextProject = nextState.projects.find((item) => item.id === projectId);
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
dispatchRecommendation: recommendation,
|
||||
dispatchPlan: recommendation.dispatchPlan,
|
||||
collaborationGate: buildCollaborationGate(nextProject),
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "@/components/app-ui";
|
||||
import { requirePageSession } from "@/lib/boss-auth";
|
||||
import { listDispatchPlansByProject, readState } from "@/lib/boss-data";
|
||||
import { latestPendingDispatchPlan } from "@/lib/dispatch-plan-ui";
|
||||
import { latestPendingDispatchPlan, latestRejectedDispatchPlan } from "@/lib/dispatch-plan-ui";
|
||||
import { formatTimestampLabel, getProjectDetailView } from "@/lib/boss-projections";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -29,6 +29,10 @@ export default async function ProjectChatPage({
|
||||
const pendingDispatchPlan = detail?.project.isGroup
|
||||
? latestPendingDispatchPlan(await listDispatchPlansByProject(projectId))
|
||||
: null;
|
||||
const rejectedDispatchPlan =
|
||||
detail?.project.isGroup && !pendingDispatchPlan
|
||||
? latestRejectedDispatchPlan(await listDispatchPlansByProject(projectId))
|
||||
: null;
|
||||
|
||||
if (!detail) notFound();
|
||||
|
||||
@@ -165,6 +169,18 @@ export default async function ProjectChatPage({
|
||||
}
|
||||
: null
|
||||
}
|
||||
initialRejectedDispatchPlan={
|
||||
rejectedDispatchPlan
|
||||
? {
|
||||
planId: rejectedDispatchPlan.planId,
|
||||
summary: rejectedDispatchPlan.summary,
|
||||
targets: (rejectedDispatchPlan.targets ?? []).map((target) => ({
|
||||
projectId: target.projectId,
|
||||
threadDisplayName: target.threadDisplayName,
|
||||
})),
|
||||
}
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user