CaroFernando commented on code in PR #8753:
URL: https://github.com/apache/texera/pull/8753#discussion_r4343788612


##########
agent-service/src/agent/texera-agent.ts:
##########
@@ -498,17 +498,23 @@ export class TexeraAgent {
   }
 
   async sendMessage(userMessage: string, messageSource?: "chat" | "feedback"): 
Promise<AgentMessageResult> {
-    const messageId = 
`msg-${this.agentId}-${++this.messageCounter}-${Date.now()}`;
-    let stepIndex = 0;
-
-    await this.refreshWorkflowFromBackend();
-
+    // State flips synchronously, before the first await below, so a second
+    // prompt arriving while this one is mid-flight (e.g. during
+    // refreshWorkflowFromBackend) sees GENERATING and is rejected instead of
+    // interleaving with this run's head/abortController/currentMessageId 
(#8711).
+    if (this.state !== AgentStateEnum.AVAILABLE) {

Review Comment:
   Fixed in c15d5a2. stop() now returns early unless state is GENERATING. The 
WS stop handler also used to broadcast STOPPING unconditionally, which would 
have left the client showing an idle agent as stopping, so it now broadcasts 
agent.getState(). New tests:
   
   unit: an idle stop is a no-op, and a stop while generating still moves to 
STOPPING
   unit: sendMessage succeeds after an idle stop
   WS: an idle stop reports AVAILABLE
   
   All three fail with the guard removed. Checked live too: POST /stop on an 
idle agent, then a prompt, runs normally.



##########
agent-service/src/agent/texera-agent.ts:
##########
@@ -498,17 +498,23 @@ export class TexeraAgent {
   }
 
   async sendMessage(userMessage: string, messageSource?: "chat" | "feedback"): 
Promise<AgentMessageResult> {
-    const messageId = 
`msg-${this.agentId}-${++this.messageCounter}-${Date.now()}`;
-    let stepIndex = 0;
-
-    await this.refreshWorkflowFromBackend();
-
+    // State flips synchronously, before the first await below, so a second
+    // prompt arriving while this one is mid-flight (e.g. during
+    // refreshWorkflowFromBackend) sees GENERATING and is rejected instead of
+    // interleaving with this run's head/abortController/currentMessageId 
(#8711).
+    if (this.state !== AgentStateEnum.AVAILABLE) {
+      throw new Error("Agent is busy processing another message");
+    }
+    this.state = AgentStateEnum.GENERATING;
     this.abortController = new AbortController();
 
-    this.state = AgentStateEnum.GENERATING;
+    const messageId = 
`msg-${this.agentId}-${++this.messageCounter}-${Date.now()}`;
+    let stepIndex = 0;
 
     this.currentMessageId = messageId;
 
+    await this.refreshWorkflowFromBackend();

Review Comment:
   Agreed, moved refreshWorkflowFromBackend() inside the try in c15d5a2, so the 
finally now resets state no matter what.



##########
agent-service/src/agent/texera-agent.spec.ts:
##########
@@ -817,6 +817,32 @@ describe("sendMessage", () => {
     expect(observed).toEqual([AgentState.GENERATING]);
     expect(agent.getState()).toBe(AgentState.AVAILABLE);
   });
+
+  test("a second sendMessage while one is in flight is rejected, not 
interleaved (#8711)", async () => {
+    let release!: () => void;
+    const gate = new Promise<void>(r => (release = r));
+    const model = new MockLanguageModelV4({
+      doGenerate: async () => {
+        await gate;
+        return {
+          content: [{ type: "text", text: "x" }],
+          finishReason: finish("stop"),
+          usage: usage(1, 1),
+          warnings: [],
+        } as any;
+      },
+    });
+    const agent = makeAgentWith(model);
+    const first = agent.sendMessage("first");
+    await new Promise(r => setTimeout(r, 5));

Review Comment:
   Removed in c15d5a2, so the test now pins the ordering fix as well as the 
guard.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to