mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-17 04:44:40 +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.team.model.TeamTaskStatus;
|
||||||
import vip.mate.tool.document.GeneratedFileCache;
|
import vip.mate.tool.document.GeneratedFileCache;
|
||||||
import vip.mate.workspace.conversation.ConversationService;
|
import vip.mate.workspace.conversation.ConversationService;
|
||||||
|
import vip.mate.workspace.conversation.model.MessageEntity;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -424,8 +425,11 @@ public class TeamDispatchService {
|
|||||||
/** Per-prerequisite and whole-section caps keeping the envelope bounded. */
|
/** Per-prerequisite and whole-section caps keeping the envelope bounded. */
|
||||||
static final int MAX_PREREQ_RESULT_CHARS = 1500;
|
static final int MAX_PREREQ_RESULT_CHARS = 1500;
|
||||||
static final int MAX_PREREQ_SECTION_CHARS = 6000;
|
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) {
|
private String buildDispatchContent(TeamTaskEntity task) {
|
||||||
StringBuilder sb = new StringBuilder(1024);
|
StringBuilder sb = new StringBuilder(1024);
|
||||||
sb.append("[Assigned team task #").append(task.getTaskNumber())
|
sb.append("[Assigned team task #").append(task.getTaskNumber())
|
||||||
@ -434,6 +438,7 @@ public class TeamDispatchService {
|
|||||||
if (task.getDescription() != null && !task.getDescription().isBlank()) {
|
if (task.getDescription() != null && !task.getDescription().isBlank()) {
|
||||||
sb.append("\n").append(task.getDescription()).append('\n');
|
sb.append("\n").append(task.getDescription()).append('\n');
|
||||||
}
|
}
|
||||||
|
appendLeadAttachmentContext(sb, task);
|
||||||
appendPrerequisiteResults(sb, task);
|
appendPrerequisiteResults(sb, task);
|
||||||
sb.append("""
|
sb.append("""
|
||||||
|
|
||||||
@ -447,6 +452,57 @@ public class TeamDispatchService {
|
|||||||
return sb.toString();
|
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
|
* Hand the member everything its prerequisites produced: result summaries
|
||||||
* and deliverable links, so upstream output flows downstream without the
|
* 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.TeamTaskCommentEntity;
|
||||||
import vip.mate.team.model.TeamTaskStatus;
|
import vip.mate.team.model.TeamTaskStatus;
|
||||||
import vip.mate.workspace.conversation.ConversationService;
|
import vip.mate.workspace.conversation.ConversationService;
|
||||||
|
import vip.mate.workspace.conversation.model.MessageEntity;
|
||||||
|
|
||||||
import java.util.List;
|
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.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
import static org.mockito.ArgumentMatchers.startsWith;
|
import static org.mockito.ArgumentMatchers.startsWith;
|
||||||
|
import static org.mockito.ArgumentCaptor.forClass;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -357,6 +359,35 @@ class TeamDispatchServiceTest {
|
|||||||
eq("team_worker"));
|
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
|
@Test
|
||||||
@DisplayName("an interrupted run whose task was cancelled produces no failed event")
|
@DisplayName("an interrupted run whose task was cancelled produces no failed event")
|
||||||
void interruptedCancelledRunStaysSilent() {
|
void interruptedCancelledRunStaysSilent() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user