mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(team): pass lead attachments to workers (#612)
This commit is contained in:
parent
9ba188b4cb
commit
37ddcf185f
@ -17,6 +17,7 @@ import vip.mate.team.model.TeamTaskEventEntity;
|
||||
import vip.mate.team.model.TeamTaskStatus;
|
||||
import vip.mate.tool.document.GeneratedFileCache;
|
||||
import vip.mate.workspace.conversation.ConversationService;
|
||||
import vip.mate.workspace.conversation.model.MessageEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -424,8 +425,11 @@ public class TeamDispatchService {
|
||||
/** Per-prerequisite and whole-section caps keeping the envelope bounded. */
|
||||
static final int MAX_PREREQ_RESULT_CHARS = 1500;
|
||||
static final int MAX_PREREQ_SECTION_CHARS = 6000;
|
||||
static final int LEAD_ATTACHMENT_CONTEXT_MESSAGES = 12;
|
||||
static final int MAX_LEAD_ATTACHMENT_ITEM_CHARS = 1200;
|
||||
static final int MAX_LEAD_ATTACHMENT_SECTION_CHARS = 6000;
|
||||
|
||||
/** The full instruction envelope the member receives; it cannot see the lead's conversation. */
|
||||
/** The full instruction envelope the member receives. */
|
||||
private String buildDispatchContent(TeamTaskEntity task) {
|
||||
StringBuilder sb = new StringBuilder(1024);
|
||||
sb.append("[Assigned team task #").append(task.getTaskNumber())
|
||||
@ -434,6 +438,7 @@ public class TeamDispatchService {
|
||||
if (task.getDescription() != null && !task.getDescription().isBlank()) {
|
||||
sb.append("\n").append(task.getDescription()).append('\n');
|
||||
}
|
||||
appendLeadAttachmentContext(sb, task);
|
||||
appendPrerequisiteResults(sb, task);
|
||||
sb.append("""
|
||||
|
||||
@ -447,6 +452,57 @@ public class TeamDispatchService {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Child worker conversations are isolated from the lead transcript, so
|
||||
* upload paths from the lead turn must be copied into the dispatch
|
||||
* envelope explicitly. Only rendered attachment/media rows with local
|
||||
* paths are included; ordinary lead chat text stays out of the member
|
||||
* prompt.
|
||||
*/
|
||||
void appendLeadAttachmentContext(StringBuilder sb, TeamTaskEntity task) {
|
||||
String leadConversationId = task.getLeadConversationId();
|
||||
if (leadConversationId == null || leadConversationId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
List<MessageEntity> messages = conversationService.listRecentMessages(
|
||||
leadConversationId, LEAD_ATTACHMENT_CONTEXT_MESSAGES);
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
StringBuilder section = new StringBuilder();
|
||||
for (MessageEntity message : messages) {
|
||||
if (message == null || message.getContentParts() == null
|
||||
|| message.getContentParts().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
String rendered = conversationService.renderMessageContent(message, true);
|
||||
if (rendered == null || rendered.isBlank() || !hasRenderedAttachmentPath(rendered)) {
|
||||
continue;
|
||||
}
|
||||
section.append("- ")
|
||||
.append(truncate(rendered.strip(), MAX_LEAD_ATTACHMENT_ITEM_CHARS)
|
||||
.replace("\n", "\n "))
|
||||
.append('\n');
|
||||
}
|
||||
if (section.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sb.append("\n[Lead conversation attachments]\n")
|
||||
.append(truncate(section.toString(), MAX_LEAD_ATTACHMENT_SECTION_CHARS))
|
||||
.append("Use these paths when this task refers to files uploaded in the lead conversation.\n");
|
||||
}
|
||||
|
||||
private static boolean hasRenderedAttachmentPath(String rendered) {
|
||||
if (!rendered.contains("路径:")) {
|
||||
return false;
|
||||
}
|
||||
return rendered.contains("[附件]")
|
||||
|| rendered.contains("[图片]")
|
||||
|| rendered.contains("[视频]")
|
||||
|| rendered.contains("[音频]")
|
||||
|| rendered.contains("[3D 模型]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Hand the member everything its prerequisites produced: result summaries
|
||||
* and deliverable links, so upstream output flows downstream without the
|
||||
|
||||
@ -10,6 +10,7 @@ import vip.mate.team.model.TeamTaskEntity;
|
||||
import vip.mate.team.model.TeamTaskCommentEntity;
|
||||
import vip.mate.team.model.TeamTaskStatus;
|
||||
import vip.mate.workspace.conversation.ConversationService;
|
||||
import vip.mate.workspace.conversation.model.MessageEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -19,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.ArgumentCaptor.forClass;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@ -357,6 +359,35 @@ class TeamDispatchServiceTest {
|
||||
eq("team_worker"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("member dispatch envelope carries lead conversation upload paths")
|
||||
void runTaskCarriesLeadConversationUploadPaths() {
|
||||
TeamTaskEntity assigned = task(1L, MEMBER_A);
|
||||
assigned.setDescription("请根据刚上传的需求.docx拆解任务");
|
||||
assigned.setStatus(TeamTaskStatus.IN_PROGRESS);
|
||||
TeamTaskEntity done = task(1L, MEMBER_A);
|
||||
done.setStatus(TeamTaskStatus.COMPLETED);
|
||||
MessageEntity uploadTurn = new MessageEntity();
|
||||
uploadTurn.setRole("user");
|
||||
uploadTurn.setContentParts("[{\"type\":\"file\"}]");
|
||||
when(conversationService.listRecentMessages("lead-conv", TeamDispatchService.LEAD_ATTACHMENT_CONTEXT_MESSAGES))
|
||||
.thenReturn(List.of(uploadTurn));
|
||||
when(conversationService.renderMessageContent(uploadTurn, true))
|
||||
.thenReturn("请看附件\n[附件] 需求.docx(路径: /workspace/uploads/lead-conv/需求.docx)");
|
||||
when(taskService.getTask(1L)).thenReturn(assigned, done, done);
|
||||
when(taskService.completeTask(eq(1L), isNull(), anyString())).thenReturn(List.of());
|
||||
when(agentService.chatWithUsage(eq(MEMBER_A), anyString(), anyString()))
|
||||
.thenReturn(AgentService.ChatResult.contentOnly("all done"));
|
||||
|
||||
service.runTask(TEAM_ID, assigned);
|
||||
|
||||
var prompt = forClass(String.class);
|
||||
verify(agentService).chatWithUsage(eq(MEMBER_A), prompt.capture(), startsWith("team-task-"));
|
||||
assertTrue(prompt.getValue().contains("[Lead conversation attachments]"));
|
||||
assertTrue(prompt.getValue().contains("需求.docx"));
|
||||
assertTrue(prompt.getValue().contains("/workspace/uploads/lead-conv/需求.docx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("an interrupted run whose task was cancelled produces no failed event")
|
||||
void interruptedCancelledRunStaysSilent() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user