diff --git a/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java b/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java index 26670263..ce7fff4e 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java @@ -1630,7 +1630,6 @@ public class ChatController { queuedConversation.getWorkspaceId(), null, baseUrl, preConsumedInput.requesterUserId()) .withOriginMessageId(queuedOriginMessageId) .withSelectedGoalId(preConsumedInput.selectedGoalId()); - queuedOrigin = captureWebGoal(queuedOrigin, agentId); Disposable disposable = agentService.chatStructuredStream(agentId, queuedMessage, conversationId, preConsumedInput.createdBy(), null, queuedOrigin) .doOnNext(delta -> { if (emitterDone.get()) return; diff --git a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalApprovalRunService.java b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalApprovalRunService.java index fe32c8b3..fc20e8c8 100644 --- a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalApprovalRunService.java +++ b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalApprovalRunService.java @@ -38,7 +38,7 @@ public class GoalApprovalRunService { public ChatOrigin captureSelectedGoal(ChatOrigin origin) { if (origin == null || origin.cronOrigin() || origin.requesterUserId() == null || origin.conversationId() == null || origin.agentId() == null || origin.workspaceId() == null - || origin.selectedGoalId() != null + || (origin.selectedGoalId() != null && origin.selectedGoalId() > 0) || (origin.executionAttribution() != null && origin.executionAttribution().goalAttemptId() != null)) return origin; var selected = jdbc.queryForList(""" diff --git a/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerDurableQueueTest.java b/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerDurableQueueTest.java index bb51dadd..13e1c726 100644 --- a/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerDurableQueueTest.java +++ b/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerDurableQueueTest.java @@ -82,7 +82,8 @@ class ChatControllerDurableQueueTest { .thenReturn(reactor.core.publisher.Flux.never()); var runs = mock(vip.mate.goal.service.GoalApprovalRunService.class); when(runs.queuedSelectionStillCurrent(any())).thenReturn(true); - when(runs.captureSelectedGoal(any())).thenAnswer(invocation -> invocation.getArgument(0)); + when(runs.captureSelectedGoal(any())).thenAnswer(invocation -> + ((vip.mate.agent.context.ChatOrigin) invocation.getArgument(0)).withSelectedGoalId(7L)); ChatController controller = new ChatController(agents, conversations, mock(ApprovalWorkflowService.class), streams, new ObjectMapper(), mock(ConversationCompletionPublisher.class), mock(MemoryOwnerResolver.class), mock(ChatUploadLocationResolver.class), mock(OfficePreviewService.class), queue); @@ -198,7 +199,8 @@ class ChatControllerDurableQueueTest { var runs = mock(vip.mate.goal.service.GoalApprovalRunService.class); when(runs.hasManagedGoalHistory("conv", "2")).thenReturn(true); when(runs.queuedSelectionStillCurrent(any())).thenReturn(true); - when(runs.captureSelectedGoal(any())).thenAnswer(invocation -> invocation.getArgument(0)); + when(runs.captureSelectedGoal(any())).thenAnswer(invocation -> + ((vip.mate.agent.context.ChatOrigin) invocation.getArgument(0)).withSelectedGoalId(7L)); when(agents.chatStructuredStream(eq(2L), eq("next"), eq("conv"), eq("alice"), any(), any())) .thenReturn(reactor.core.publisher.Flux.never()); ChatController controller = new ChatController(agents, conversations, mock(ApprovalWorkflowService.class), @@ -211,8 +213,11 @@ class ChatControllerDurableQueueTest { new org.springframework.web.servlet.mvc.method.annotation.SseEmitter(), new java.util.concurrent.atomic.AtomicBoolean(false), "alice", "http://localhost"); + var origin = org.mockito.ArgumentCaptor.forClass(vip.mate.agent.context.ChatOrigin.class); org.mockito.Mockito.verify(agents, org.mockito.Mockito.timeout(2000)) - .chatStructuredStream(eq(2L), eq("next"), eq("conv"), eq("alice"), any(), any()); + .chatStructuredStream(eq(2L), eq("next"), eq("conv"), eq("alice"), any(), origin.capture()); + assertThat(origin.getValue().selectedGoalId()).isZero(); + org.mockito.Mockito.verify(runs, org.mockito.Mockito.never()).captureSelectedGoal(any()); org.mockito.Mockito.verify(queue).consume(eq(92L), any(), any()); org.mockito.Mockito.verify(queue).consume(eq(93L), any(), any()); } diff --git a/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java b/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java index e8956490..cd86e4c3 100644 --- a/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java +++ b/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java @@ -152,8 +152,8 @@ class GoalJsonHttpRuntimeIntegrationTest { if (!plan && entry.equals("sync") && accepted) { var explicitlyUnselected = ChatOrigin.web(conversation, username, 1L, null, null, userId) .withAgent(agentId).withSelectedGoalId(0L); - assertEquals(0L, approvalRuns.captureSelectedGoal(explicitlyUnselected).selectedGoalId(), - "An explicit queue snapshot must not be recaptured into a later Goal"); + assertEquals(goal.getId(), approvalRuns.captureSelectedGoal(explicitlyUnselected).selectedGoalId(), + "An ordinary in-flight request may tighten an explicit zero before approval persistence"); assertFalse(approvalRuns.queuedSelectionStillCurrent(explicitlyUnselected), "An unselected queue snapshot must become stale when a managed Goal appears"); }