diff --git a/metagpt/prompts/di/engineer2.py b/metagpt/prompts/di/engineer2.py index 96392e334..6a4392623 100644 --- a/metagpt/prompts/di/engineer2.py +++ b/metagpt/prompts/di/engineer2.py @@ -79,12 +79,12 @@ Note: 21. When planning, consider whether images are needed. If you are developing a showcase website, start by using ImageGetter.get_image to obtain the necessary images. 22. When planning, merge multiple tasks that operate on the same file into a single task. For example, create one task for writing unit tests for all functions in a class. Also in using the editor, merge multiple tasks that operate on the same file into a single task. 23. When create unit tests for a code file, use Editor.read() to read the code file before planing. And create one plan to writing the unit test for the whole file. -24. Follow the Sytem Design and Project Schedule if exists. Otherwise, use default template folder of Vite, React, MUI and Tailwind CSS. If the template does not exist, use native HTML. -25. When writing Vue/React project: The Vue template is in the {vue_template_path}, the React template is in the {react_template_path}. +24. Follow the Sytem Design and Project Schedule if exists. Otherwise, use default template folder of Vite, React, MUI and Tailwind CSS. The React template is in the "{react_template_path}" and Vue template is in the "{vue_template_path}". If the template does not exist, use native HTML. +25. When writing Vue/React project: 25.1. Create the project folder first. Use cmd " mkdir -p {{project_name}} " 25.2. Copy a Vue/React template to your project and view all files. This must be a single respond. Use cmd "cp -r {{template_folder}}/* {{workspace}}/{{project_name}}/ && cd {{workspace}}/{{project_name}} && pwd && tree -f". -25.3. Read the content of each file and use the write_new_code command to rewrite the code. Be sure you are in the {{project_name}}. Reorganize the file structure to match the project template if it differs from the system design. -25.4. After finish the project. use "npm install" and "npm run build" to build the project. +25.3. Read the content of each file and use the write_new_code command to rewrite the code. Be sure you are in the {{project_name}}. +25.4. After finish the project. use "pnpm install" and "pnpm run build" to build the project and then use Deployer.deploy_to_public to deploy the project to the public. 26. Engineer2.write_new_code is used to write or rewrite the code, which will modify the whole file. Editor.edit_file_by_replace is used to edit a small part of the file. """.format( vue_template_path=VUE_TEMPLATE_PATH.absolute(), diff --git a/metagpt/roles/di/engineer2.py b/metagpt/roles/di/engineer2.py index 799361df6..6776fdf3a 100644 --- a/metagpt/roles/di/engineer2.py +++ b/metagpt/roles/di/engineer2.py @@ -18,6 +18,7 @@ from metagpt.roles.di.role_zero import RoleZero from metagpt.schema import UserMessage from metagpt.strategy.experience_retriever import ENGINEER_EXAMPLE from metagpt.tools.libs.cr import CodeReview +from metagpt.tools.libs.deployer import Deployer from metagpt.tools.libs.git import git_create_pull from metagpt.tools.libs.image_getter import ImageGetter from metagpt.tools.libs.terminal import Terminal @@ -33,7 +34,7 @@ class Engineer2(RoleZero): goal: str = "Take on game, app, and web development." instruction: str = ENGINEER2_INSTRUCTION terminal: Terminal = Field(default_factory=Terminal, exclude=True) - + deployer: Deployer = Field(default_factory=Deployer, exclude=True) tools: list[str] = [ "Plan", "Editor", @@ -45,6 +46,7 @@ class Engineer2(RoleZero): "Engineer2", "CodeReview", "ImageGetter", + "Deployer", ] # SWE Agent parameter run_eval: bool = False @@ -86,6 +88,7 @@ class Engineer2(RoleZero): "Terminal.run_command": self._eval_terminal_run, "RoleZero.ask_human": self._end, "RoleZero.reply_to_human": self._end, + "Deployer.deploy_to_public": self.deployer.deploy_to_public, } ) else: @@ -98,6 +101,7 @@ class Engineer2(RoleZero): "CodeReview.review": cr.review, "CodeReview.fix": cr.fix, "Terminal.run_command": self.terminal.run_command, + "Deployer.deploy_to_public": self.deployer.deploy_to_public, } ) diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index fda29add9..c284db702 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -76,7 +76,7 @@ class RoleZero(Role): tools: list[str] = [] # Use special symbol [""] to indicate use of all registered tools tool_recommender: Optional[ToolRecommender] = None tool_execution_map: Annotated[dict[str, Callable], Field(exclude=True)] = {} - special_tool_commands: list[str] = ["Plan.finish_current_task", "end", "Bash.run", "RoleZero.ask_human"] + special_tool_commands: list[str] = ["Plan.finish_current_task", "end", "Terminal.run_command", "RoleZero.ask_human"] # List of exclusive tool commands. # If multiple instances of these commands appear, only the first occurrence will be retained. exclusive_tool_commands: list[str] = [ @@ -543,7 +543,7 @@ class RoleZero(Role): return end_output return human_response # output from bash.run may be empty, add decorations to the output to ensure visibility. - elif cmd["command_name"] == "Bash.run": + elif cmd["command_name"] == "Terminal.run_command": tool_obj = self.tool_execution_map[cmd["command_name"]] tool_output = await tool_obj(**cmd["args"]) if len(tool_output) <= 10: diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index da66f4796..794fe19f3 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -839,19 +839,27 @@ Explanation: I will first need to read the system design document and the projec } ] ``` -## example 2.1 +## example 2 User Requirement: Implement the core game project in Vue/React framework. Explanation: This is a project that needs to be implemented using Vue.js. Therefore, I need to copy the Vue/React template to the project folder first. -## example 2.2 +## example 3 User Requirement: Writing code. +The template is : +├── public +├── src +│ ├── App.jsx +│ ├── index.css +│ └── main.jsx +└── vite.config.js + Here's the plan: [Optional] Read the original file in the template if they exist. [Optional] Obtain images before coding. -1. **Task 1**: Rewrite `App.vue` - This file will contain the Vue structure necessary for the game's UI, the game logic and UI interactions. +1. **Task 1**: Rewrite `App.jsx` - This file will contain the Vue structure necessary for the game's UI, the game logic and UI interactions. 2. **Task 2**: Rewrite `style.css` - This file will define the CSS styles to make the game visually appealing and responsive. Default use tailwindcss. 3. **Task 3**: Rewrite `main.js` - This file is the main entry of Vue project, including the main Vue instance, global styles, and the router. -[Optional] Install the dependencies after finishing project. +If the project is a Vue or React Project, install the dependencies after finishing project. And then deploy the project to the public. ```json [ { @@ -894,15 +902,15 @@ Here's the plan: "command_name": "Plan.append_task", "args": { "task_id": "5", - "dependent_task_ids": ["2","3","4"], - "instruction": "Install the necessary dependencies and configure the project structure.", + "dependent_task_ids": [], + "instruction": "Install the necessary dependencies, configure the project structure and deploy it to the public", "assignee": "Alex" } } ] ``` -## example 3 +## example 4 Explanation: Take on one task, such as writing or rewriting a file. Upon completion, finish current task. ```json @@ -919,8 +927,31 @@ Explanation: Take on one task, such as writing or rewriting a file. Upon complet } ] ``` +## example 5 +Explanation: The project have been completed. And this project is a Vue/React Project,so i will deploy the project to the public. -## example 4 +```json +[ + { + "command_name": "Terminal.run_command", + "args": { + "cmd": "pnpm install && pnpm run build" + } + } +] +## example 6 +Explanation: After install the project. I will deploy the project to the public. +```json +[ + { + "command_name": "Deployer.deploy_to_public, + "args": { + "dist_dir": "{{project_path}}/dist" + } + } +] + +## example 7 I have received a GitHub issue URL. I will use browser to review the detailed information of this issue in order to understand the problem. ```json @@ -934,7 +965,7 @@ I will use browser to review the detailed information of this issue in order to ] ``` -## example 6 +## example 8 I need to locating the `openai_api.py` file, so I will search for the `openai_api.py` file. ```json [ @@ -949,7 +980,7 @@ I need to locating the `openai_api.py` file, so I will search for the `openai_ap -## example 7 +## example 9 I have located the openai_api.py file. I want to edit this file, so I will open it first. ```json [ @@ -962,7 +993,7 @@ I have located the openai_api.py file. I want to edit this file, so I will open ] ``` -## example 8 +## example 10 I have opened the openai_api.py file. However, the range of lines shown is from 001 to 100, and I want to see more. Therefore, I want to use the scroll_down command to view additional lines. ```json [ @@ -973,7 +1004,7 @@ I have opened the openai_api.py file. However, the range of lines shown is from ] ``` -## example 9 +## example 11 I want to change the key bindings from (w/s) to the arrow keys (up, down). And add the space bar to pause. the previous file look like: 142| while not self.is_game_over(): @@ -1000,7 +1031,7 @@ Editor tool is exclusive. If I use this tool, I cannot use any other commands in ] ``` -## example 10 +## example 12 I want to add a score variable in the initialization of the game. the previous file look like: 028| if restart: @@ -1032,7 +1063,7 @@ After executing the command, the file will be: 033| self.location = (0,0) In the next turn, I will try to add another code snippet -## example 11 +## example 13 Create a pull request (Optional): Merge the changes from the new branch into the master branch. Thought: Now that the changes have been pushed to the remote repository, due to the user's requirement, let's create a pull request to merge the changes into the master branch. @@ -1053,7 +1084,7 @@ Thought: Now that the changes have been pushed to the remote repository, due to ] ``` -## example 12 +## example 14 The requirement is to create a product website featuring goods such as caps, dresses, and T-shirts. I believe pictures would improve the site, so I will get the images first. ```json diff --git a/metagpt/tools/libs/deployer.py b/metagpt/tools/libs/deployer.py index c30ad0176..4f1ac0ecf 100644 --- a/metagpt/tools/libs/deployer.py +++ b/metagpt/tools/libs/deployer.py @@ -1,11 +1,26 @@ from metagpt.tools.tool_registry import register_tool -from metagpt.utils.report import ServerReporter # An un-implemented tool reserved for deploying a local service to public -@register_tool() +@register_tool( + include_functions=[ + "deploy_to_public", + ] +) class Deployer: """Deploy a local service to public. Used only for final deployment, you should NOT use it for development and testing.""" - def deploy_to_public(self, local_url: str): - ServerReporter().report(local_url, "local_url") + async def static_server(self, src_path: str) -> str: + """This function will be implemented in the remote service.""" + return f"http://127.0.0.1:{8000}/index.html" + + async def deploy_to_public(self, dist_dir: str): + """ + Deploy a web project to public. + Args: + dist_dir (str): The dist directory of the web project after run build. + >>> + deployer = Deployer("2048_game/dist") + """ + url = await self.static_server(dist_dir) + return "The Project is deployed to: " + url + "\n Deployment successed!" diff --git a/metagpt/tools/libs/terminal.py b/metagpt/tools/libs/terminal.py index a621e4b0e..0150e26e6 100644 --- a/metagpt/tools/libs/terminal.py +++ b/metagpt/tools/libs/terminal.py @@ -26,6 +26,11 @@ class Terminal: self.stdout_queue = Queue(maxsize=1000) self.observer = TerminalReporter() self.process: Optional[asyncio.subprocess.Process] = None + # The cmd in forbidden_terminal_commands will be replace by pass ana return the advise. example:{"cmd":"forbidden_reason/advice"} + self.forbidden_commands = { + "npm run dev": "Use Deployer.deploy_to_public instead.", + "pnpm run dev": "Use Deployer.deploy_to_public instead.", + } async def _start_process(self): # Start a persistent shell process @@ -60,6 +65,14 @@ class Terminal: if self.process is None: await self._start_process() + output = "" + # Remove forbidden commands + for cmd_name, reason in self.forbidden_commands.items(): + # 'true' is a pass command in linux terminal. + if cmd_name in cmd: + cmd = cmd.replace(cmd_name, "true") + output += f"{cmd_name} is failed to executed. {reason}\n" + # Send the command self.process.stdin.write((cmd + self.command_terminator).encode()) self.process.stdin.write( @@ -68,9 +81,10 @@ class Terminal: await self.process.stdin.drain() if daemon: asyncio.create_task(self._read_and_process_output(cmd)) - return "" else: - return await self._read_and_process_output(cmd) + output += await self._read_and_process_output(cmd) + + return output async def execute_in_conda_env(self, cmd: str, env, daemon=False) -> str: """