feat: add group repair and dispatch rejection flows

This commit is contained in:
kris
2026-03-31 03:56:28 +08:00
parent 9c02ebb574
commit 4336dc22a7
21 changed files with 832 additions and 83 deletions

View File

@@ -4175,6 +4175,50 @@ export async function confirmDispatchPlan(input: {
});
}
export async function rejectDispatchPlan(input: {
groupProjectId: string;
planId: string;
rejectedBy: string;
}) {
const result = await mutateState((state) => {
const groupProjectId = input.groupProjectId.trim();
if (!groupProjectId) throw new Error("PROJECT_NOT_FOUND");
const groupProject = state.projects.find((item) => item.id === groupProjectId);
if (!groupProject) throw new Error("PROJECT_NOT_FOUND");
if (!groupProject.isGroup) throw new Error("PROJECT_NOT_GROUP_CHAT");
requireDispatchActorSession(state, input.rejectedBy);
const plan = state.dispatchPlans.find((item) => item.planId === input.planId);
if (!plan) throw new Error("DISPATCH_PLAN_NOT_FOUND");
if (plan.groupProjectId !== groupProjectId) {
throw new Error("DISPATCH_PLAN_PROJECT_MISMATCH");
}
if (plan.status === "dispatched") {
throw new Error("DISPATCH_PLAN_ALREADY_DISPATCHED");
}
if (plan.status !== "rejected") {
plan.status = "rejected";
}
groupProject.approvalState = "rejected";
const notice =
pushProjectLedgerMessage(state, groupProjectId, {
sender: "master",
senderLabel: "主 Agent",
body: "已拒绝主 Agent 推荐,本次不会下发到任何线程。",
kind: "system_notice",
}) ?? null;
return {
plan: { ...plan },
notice: notice ? { ...notice } : null,
};
});
publishBossEvent("project.messages.updated", { projectId: input.groupProjectId });
publishBossEvent("conversation.updated", { projectId: input.groupProjectId });
return result;
}
export async function createDispatchExecutionsFromPlan(input: {
planId: string;
confirmedBy: string;
@@ -6266,17 +6310,13 @@ export async function createIndependentGroupChat(input: {
return project;
}
function createGroupChatFromProjectIds(
function resolveGroupChatThreadProjects(
state: BossState,
input: {
requestedProjectIds: string[];
createdBy: string;
defaultRiskLevel?: Project["riskLevel"];
},
requestedProjectIds: string[],
) {
const memberProjects: Project[] = [];
const seenProjectIds = new Set<string>();
for (const projectId of input.requestedProjectIds) {
for (const projectId of requestedProjectIds) {
if (!projectId || seenProjectIds.has(projectId)) {
continue;
}
@@ -6291,6 +6331,77 @@ function createGroupChatFromProjectIds(
}
memberProjects.push(memberProject);
}
return memberProjects;
}
export async function replaceGroupChatMembers(input: {
projectId: string;
memberProjectIds: string[];
requestedBy: string;
}) {
const result = await mutateState((state) => {
const groupProject = state.projects.find((item) => item.id === input.projectId);
if (!groupProject) {
throw new Error("PROJECT_NOT_FOUND");
}
if (!groupProject.isGroup) {
throw new Error("PROJECT_NOT_GROUP_CHAT");
}
const memberProjects = resolveGroupChatThreadProjects(state, input.memberProjectIds);
if (memberProjects.length < 2) {
throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS");
}
const now = nowIso();
groupProject.groupMembers = memberProjects.map((memberProject) => ({
projectId: memberProject.id,
deviceId: memberProject.deviceIds[0] ?? memberProject.id,
threadId: memberProject.threadMeta.threadId,
threadDisplayName: memberProject.threadMeta.threadDisplayName,
folderName: memberProject.threadMeta.folderName,
}));
groupProject.deviceIds = dedupeStrings(groupProject.groupMembers.map((member) => member.deviceId));
groupProject.threadMeta.activityIconCount = Math.max(1, groupProject.groupMembers.length);
groupProject.threadMeta.folderName = "群聊";
groupProject.threadMeta.updatedAt = now;
groupProject.updatedAt = now;
groupProject.lastMessageAt = now;
groupProject.approvalState = "not_required";
const memberLabel = memberProjects
.map((project) => project.threadMeta.threadDisplayName || project.name)
.join("、");
pushProjectLedgerMessage(state, groupProject.id, {
sender: "master",
senderLabel: "主 Agent",
body: `已更新群成员:${memberLabel}`,
kind: "system_notice",
sentAt: now,
});
return {
project: { ...groupProject },
groupMembers: groupProject.groupMembers.map((member) => ({ ...member })),
};
});
publishBossEvent("project.messages.updated", { projectId: input.projectId });
publishBossEvent("conversation.updated", {
projectId: input.projectId,
note: `group members updated by ${input.requestedBy}`,
});
return result;
}
function createGroupChatFromProjectIds(
state: BossState,
input: {
requestedProjectIds: string[];
createdBy: string;
defaultRiskLevel?: Project["riskLevel"];
},
) {
const memberProjects = resolveGroupChatThreadProjects(state, input.requestedProjectIds);
if (memberProjects.length < 2) {
throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS");
}