Explorar o código

Minor changes to agent state management (#1597)

* move towards event stream

* refactor agent state changes

* move agent state logic

* fix callbacks

* break on finish

* closer to working

* change frontend to accomodate new flow

* handle start action

* fix locked stream

* revert message

* logspam

* no async on close

* get rid of agent_task

* fix up closing

* better asyncio handling

* sleep to give back control

* fix key

* logspam

* update frontend agent state actions

* fix pause and cancel

* delint

* fix map

* delint

* wait for agent to finish

* fix unit test

* event stream enums

* fix merge issues

* fix lint

* fix test

* fix test

* add user message action

* add user message action

* fix up user messages

* fix main.py flow

* refactor message waiting

* lint

* fix test

* fix test

* simplify if/else

* fix state reset

* logspam

* add error status

* minor changes to control bar

* handle user messages when not awaiting

* restart agent after stopping

* Update opendevin/controller/agent_controller.py

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>

* delint

* refactor initialize

* delint

* fix dispatch

---------

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Robert Brennan hai 1 ano
pai
achega
d0967122f8

+ 1 - 2
frontend/src/App.tsx

@@ -16,7 +16,6 @@ import AgentControlBar from "./components/AgentControlBar";
 import AgentStatusBar from "./components/AgentStatusBar";
 import Terminal from "./components/terminal/Terminal";
 import { initializeAgent } from "./services/agent";
-import { getSettings } from "./services/settings";
 
 interface Props {
   setSettingOpen: (isOpen: boolean) => void;
@@ -73,7 +72,7 @@ function App(): JSX.Element {
     if (initOnce) return;
     initOnce = true;
 
-    initializeAgent(getSettings());
+    initializeAgent();
 
     Socket.registerCallback("open", [getMsgTotal]);
 

+ 6 - 13
frontend/src/components/AgentControlBar.tsx

@@ -25,11 +25,7 @@ const IgnoreTaskStateMap: { [k: string]: AgentState[] } = {
     AgentState.FINISHED,
     AgentState.AWAITING_USER_INPUT,
   ],
-  [AgentState.STOPPED]: [
-    AgentState.INIT,
-    AgentState.STOPPED,
-    AgentState.FINISHED,
-  ],
+  [AgentState.STOPPED]: [AgentState.INIT, AgentState.STOPPED],
 };
 
 interface ButtonProps {
@@ -76,18 +72,15 @@ function AgentControlBar() {
       return;
     }
 
-    let act = action;
-
-    if (act === AgentState.STOPPED) {
-      act = AgentState.STOPPED;
+    if (action === AgentState.STOPPED) {
       clearMsgs().then().catch();
       store.dispatch(clearMessages());
     } else {
       setIsLoading(true);
     }
 
-    setDesiredState(act);
-    changeAgentState(act);
+    setDesiredState(action);
+    changeAgentState(action);
   };
 
   useEffect(() => {
@@ -125,7 +118,7 @@ function AgentControlBar() {
             isLoading ||
             IgnoreTaskStateMap[AgentState.PAUSED].includes(curAgentState)
           }
-          content="Pause the agent task"
+          content="Pause the current task"
           action={AgentState.PAUSED}
           handleAction={handleAction}
           large
@@ -135,7 +128,7 @@ function AgentControlBar() {
       )}
       <ActionButton
         isDisabled={isLoading}
-        content="Restart a new agent task"
+        content="Start a new task"
         action={AgentState.STOPPED}
         handleAction={handleAction}
       >

+ 4 - 0
frontend/src/components/AgentStatusBar.tsx

@@ -31,6 +31,10 @@ const AgentStatusMap: { [k: string]: { message: string; indicator: string } } =
       message: "Agent has finished the task.",
       indicator: "bg-green-500",
     },
+    [AgentState.ERROR]: {
+      message: "Agent encountered an error.",
+      indicator: "bg-red-500",
+    },
   };
 
 function AgentStatusBar() {

+ 3 - 1
frontend/src/components/chat/ChatInterface.tsx

@@ -14,7 +14,9 @@ function ChatInterface() {
   const { curAgentState } = useSelector((state: RootState) => state.agent);
 
   const handleSendMessage = (content: string) => {
-    const isTask = curAgentState === AgentState.INIT;
+    const isTask =
+      curAgentState === AgentState.INIT ||
+      curAgentState === AgentState.FINISHED;
     dispatch(addUserMessage(content));
     sendChatMessage(content, isTask);
   };

+ 1 - 1
frontend/src/components/modals/settings/SettingsModal.tsx

@@ -74,7 +74,7 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
     const updatedSettings = getSettingsDifference(settings);
     saveSettings(settings);
     i18next.changeLanguage(settings.LANGUAGE);
-    initializeAgent(settings); // reinitialize the agent with the new settings
+    initializeAgent(); // reinitialize the agent with the new settings
 
     const sensitiveKeys = ["LLM_API_KEY"];
 

+ 3 - 2
frontend/src/services/agent.test.ts

@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest";
 
 import ActionType from "#/types/ActionType";
 import { initializeAgent } from "./agent";
-import { Settings } from "./settings";
+import { Settings, saveSettings } from "./settings";
 import Socket from "./socket";
 
 const sendSpy = vi.spyOn(Socket, "send");
@@ -21,7 +21,8 @@ describe("initializeAgent", () => {
       args: settings,
     };
 
-    initializeAgent(settings);
+    saveSettings(settings);
+    initializeAgent();
 
     expect(sendSpy).toHaveBeenCalledWith(JSON.stringify(event));
   });

+ 3 - 2
frontend/src/services/agent.ts

@@ -1,12 +1,13 @@
 import ActionType from "#/types/ActionType";
-import { Settings } from "./settings";
+import { getSettings } from "./settings";
 import Socket from "./socket";
 
 /**
  * Initialize the agent with the current settings.
  * @param settings - The new settings.
  */
-export const initializeAgent = (settings: Settings) => {
+export const initializeAgent = () => {
+  const settings = getSettings();
   const event = { action: ActionType.INIT, args: settings };
   const eventString = JSON.stringify(event);
   Socket.send(eventString);

+ 10 - 2
frontend/src/services/agentStateService.ts

@@ -1,11 +1,19 @@
 import ActionType from "#/types/ActionType";
 import AgentState from "#/types/AgentState";
 import Socket from "./socket";
+import { initializeAgent } from "./agent";
 
-export function changeAgentState(message: AgentState): void {
+const INIT_DELAY = 1000;
+
+export function changeAgentState(state: AgentState): void {
   const eventString = JSON.stringify({
     action: ActionType.CHANGE_AGENT_STATE,
-    args: { agent_state: message },
+    args: { agent_state: state },
   });
   Socket.send(eventString);
+  if (state === AgentState.STOPPED) {
+    setTimeout(() => {
+      initializeAgent();
+    }, INIT_DELAY);
+  }
 }

+ 14 - 7
opendevin/controller/agent_controller.py

@@ -54,6 +54,7 @@ class AgentController:
     state: State | None = None
     _agent_state: AgentState = AgentState.LOADING
     _cur_step: int = 0
+    _pending_talk_action: AgentTalkAction | None = None
 
     def __init__(
         self,
@@ -175,13 +176,19 @@ class AgentController:
         if isinstance(event, ChangeAgentStateAction):
             await self.set_agent_state_to(event.agent_state)  # type: ignore
         elif isinstance(event, MessageAction) and event.source == EventSource.USER:
-            # FIXME: we're hacking a message action into a user message observation, for the benefit of CodeAct
-            await self.add_history(
-                self._pending_talk_action,
-                UserMessageObservation(event.content),
-                add_to_stream=False,
-            )
-            await self.set_agent_state_to(AgentState.RUNNING)
+            if self._pending_talk_action is None:
+                await self.add_history(
+                    NullAction(), UserMessageObservation(event.content)
+                )
+            else:
+                # FIXME: we're hacking a message action into a user message observation, for the benefit of CodeAct
+                await self.add_history(
+                    self._pending_talk_action,
+                    UserMessageObservation(event.content),
+                    add_to_stream=False,
+                )
+                self._pending_talk_action = None
+                await self.set_agent_state_to(AgentState.RUNNING)
 
     async def reset_task(self):
         if self.agent_task is not None: